522 lines
16 KiB
Objective-C
522 lines
16 KiB
Objective-C
#import <Foundation/Foundation.h>
|
|
#import <sys/socket.h>
|
|
#import <netinet/in.h>
|
|
#import <arpa/inet.h>
|
|
|
|
#import "XUDPServer.h"
|
|
#import "UDPHandler.h"
|
|
|
|
#define FALLBACK_PORT_START 6001
|
|
#define FALLBACK_PORT_END 7000
|
|
#define PORT 6001
|
|
#define SEND_TIMEOUT 5.0 // 发送超时时间
|
|
|
|
@interface XUDPServer() {
|
|
@private
|
|
GCDAsyncUdpSocket *serverSocket;
|
|
dispatch_queue_t serverQueue;
|
|
dispatch_source_t restartTimer;
|
|
NSUInteger restartAttempts;
|
|
uint16_t currentPort;
|
|
NSTimer *healthCheckTimer;
|
|
}
|
|
|
|
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSDictionary *> *pendingSends;
|
|
@property (nonatomic, assign) long currentTag;
|
|
|
|
@end
|
|
|
|
@implementation XUDPServer
|
|
|
|
#pragma mark - Singleton
|
|
|
|
+ (instancetype)sharedInstance {
|
|
static XUDPServer* _sharedInstance = nil;
|
|
static dispatch_once_t oncePredicate;
|
|
dispatch_once(&oncePredicate, ^{
|
|
_sharedInstance = [[XUDPServer alloc] init];
|
|
});
|
|
return _sharedInstance;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
if (self = [super init]) {
|
|
restartAttempts = 0;
|
|
currentPort = PORT;
|
|
_currentTag = 0;
|
|
_pendingSends = [NSMutableDictionary dictionary];
|
|
|
|
// 创建串行队列,避免并发问题
|
|
serverQueue = dispatch_queue_create("com.xudpserver.queue", DISPATCH_QUEUE_SERIAL);
|
|
|
|
return self;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)start {
|
|
dispatch_async(serverQueue, ^{
|
|
[self _startInternal];
|
|
});
|
|
}
|
|
|
|
- (uint16_t)udp_port {
|
|
return currentPort;
|
|
}
|
|
|
|
- (void)_startInternal {
|
|
NSLog(@"XS- Starting UDP server on port %d", currentPort);
|
|
|
|
// 避免重复创建
|
|
if (serverSocket && !serverSocket.isClosed) {
|
|
NSLog(@"⚠️ UDP server already running on port %d", currentPort);
|
|
return;
|
|
}
|
|
|
|
[self _stopInternal];
|
|
|
|
serverSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self
|
|
delegateQueue:serverQueue];
|
|
|
|
NSError *error = nil;
|
|
|
|
// ⭐️ 启用端口重用
|
|
if (![serverSocket enableReusePort:YES error:&error]) {
|
|
NSLog(@"❌ Error enabling reuse port: %@", error);
|
|
}
|
|
|
|
// ⭐️ 设置所有必要的socket选项
|
|
[self _configureSocketOptions];
|
|
|
|
// 尝试绑定到指定端口
|
|
if (![serverSocket bindToPort:currentPort error:&error]) {
|
|
NSLog(@"❌ Error binding to port %d: %@", currentPort, error);
|
|
|
|
[serverSocket close];
|
|
serverSocket = nil;
|
|
|
|
if (error.code == 48) { // EADDRINUSE
|
|
NSLog(@"⚠️ Port %d is in use (possibly TIME_WAIT)", currentPort);
|
|
[self _tryFallbackPorts];
|
|
return;
|
|
}
|
|
|
|
[self _scheduleRestartWithBackoff];
|
|
return;
|
|
}
|
|
|
|
if (![serverSocket beginReceiving:&error]) {
|
|
NSLog(@"❌ Error starting server (recv): %@", error);
|
|
|
|
[serverSocket close];
|
|
serverSocket = nil;
|
|
|
|
[self _scheduleRestartWithBackoff];
|
|
return;
|
|
}
|
|
|
|
restartAttempts = 0;
|
|
NSLog(@"✅ UDP server started successfully on port %d", currentPort);
|
|
|
|
// ⭐️ 启动健康检查
|
|
[self _startHealthCheck];
|
|
}
|
|
|
|
// ⭐️ 配置所有socket选项
|
|
- (void)_configureSocketOptions {
|
|
if (!serverSocket) return;
|
|
|
|
int fd = [serverSocket socketFD];
|
|
if (fd == -1) {
|
|
NSLog(@"⚠️ Invalid socket file descriptor");
|
|
return;
|
|
}
|
|
|
|
// 1. 设置 SO_REUSEADDR - 允许快速重启,避免TIME_WAIT问题
|
|
int reuseAddr = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) == -1) {
|
|
NSLog(@"❌ Error setting SO_REUSEADDR: %s", strerror(errno));
|
|
}
|
|
|
|
// 2. 设置 SO_REUSEPORT - 允许多个socket绑定同一端口(某些系统)
|
|
int reusePort = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort)) == -1) {
|
|
NSLog(@"⚠️ SO_REUSEPORT not supported or error: %s", strerror(errno));
|
|
}
|
|
|
|
// 3. ⭐️ 增加接收缓冲区大小,避免缓冲区溢出
|
|
int recvBufferSize = 256 * 1024; // 256KB
|
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, sizeof(recvBufferSize)) == -1) {
|
|
NSLog(@"⚠️ Failed to set receive buffer size: %s", strerror(errno));
|
|
} else {
|
|
// 验证实际设置的大小
|
|
socklen_t optlen = sizeof(recvBufferSize);
|
|
getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, &optlen);
|
|
NSLog(@"✅ Receive buffer size set to: %d bytes", recvBufferSize);
|
|
}
|
|
|
|
// 4. ⭐️ 增加发送缓冲区大小
|
|
int sendBufferSize = 256 * 1024; // 256KB
|
|
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendBufferSize, sizeof(sendBufferSize)) == -1) {
|
|
NSLog(@"⚠️ Failed to set send buffer size: %s", strerror(errno));
|
|
} else {
|
|
socklen_t optlen = sizeof(sendBufferSize);
|
|
getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendBufferSize, &optlen);
|
|
NSLog(@"✅ Send buffer size set to: %d bytes", sendBufferSize);
|
|
}
|
|
|
|
// 5. ⭐️ 设置 SO_NOSIGPIPE - 防止写入关闭的socket时产生SIGPIPE信号
|
|
#ifdef SO_NOSIGPIPE
|
|
int noSigpipe = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe)) == -1) {
|
|
NSLog(@"⚠️ Failed to set SO_NOSIGPIPE: %s", strerror(errno));
|
|
}
|
|
#endif
|
|
|
|
// 6. ⭐️ 设置非阻塞模式(GCDAsyncUdpSocket通常已设置,但确保一下)
|
|
int flags = fcntl(fd, F_GETFL, 0);
|
|
if (flags != -1) {
|
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
}
|
|
}
|
|
|
|
- (void)stop {
|
|
dispatch_async(serverQueue, ^{
|
|
[self _stopInternal];
|
|
});
|
|
}
|
|
|
|
- (void)_stopInternal {
|
|
NSLog(@"XS- Stopping UDP server on port %d", currentPort);
|
|
|
|
[self _cancelRestartTimer];
|
|
[self _stopHealthCheck];
|
|
|
|
if (serverSocket) {
|
|
// ⭐️ 设置SO_LINGER为0,强制立即关闭,避免TIME_WAIT
|
|
int fd = [serverSocket socketFD];
|
|
if (fd != -1) {
|
|
struct linger lingerOption = {1, 0}; // l_onoff=1, l_linger=0
|
|
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lingerOption, sizeof(lingerOption)) == -1) {
|
|
NSLog(@"⚠️ Failed to set SO_LINGER: %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
if (!serverSocket.isClosed) {
|
|
[serverSocket close];
|
|
}
|
|
|
|
serverSocket = nil;
|
|
}
|
|
|
|
[_pendingSends removeAllObjects];
|
|
}
|
|
|
|
#pragma mark - Health Check
|
|
|
|
// ⭐️ 健康检查
|
|
- (void)_startHealthCheck {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self->healthCheckTimer) {
|
|
[self->healthCheckTimer invalidate];
|
|
}
|
|
|
|
self->healthCheckTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
|
|
target:self
|
|
selector:@selector(_performHealthCheck)
|
|
userInfo:nil
|
|
repeats:YES];
|
|
});
|
|
}
|
|
|
|
- (void)_stopHealthCheck {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self->healthCheckTimer) {
|
|
[self->healthCheckTimer invalidate];
|
|
self->healthCheckTimer = nil;
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)_performHealthCheck {
|
|
dispatch_async(serverQueue, ^{
|
|
if (!self->serverSocket || self->serverSocket.isClosed) {
|
|
NSLog(@"⚠️ Health check failed: socket is closed");
|
|
[self _startInternal];
|
|
return;
|
|
}
|
|
|
|
// ⭐️ 检查socket状态
|
|
int fd = [self->serverSocket socketFD];
|
|
if (fd == -1) {
|
|
NSLog(@"⚠️ Health check failed: invalid socket");
|
|
[self _startInternal];
|
|
return;
|
|
}
|
|
|
|
// ⭐️ 检查端口是否仍然绑定
|
|
struct sockaddr_in addr;
|
|
socklen_t addrLen = sizeof(addr);
|
|
if (getsockname(fd, (struct sockaddr *)&addr, &addrLen) == -1) {
|
|
NSLog(@"⚠️ Health check failed: socket not bound");
|
|
[self _startInternal];
|
|
return;
|
|
}
|
|
|
|
NSLog(@"✅ Health check passed for port %d", self->currentPort);
|
|
});
|
|
}
|
|
|
|
#pragma mark - Restart Logic
|
|
|
|
- (void)_cancelRestartTimer {
|
|
if (restartTimer) {
|
|
dispatch_source_cancel(restartTimer);
|
|
restartTimer = nil;
|
|
}
|
|
}
|
|
|
|
- (void)_scheduleRestartWithBackoff {
|
|
[self _cancelRestartTimer];
|
|
|
|
const NSUInteger maxAttempts = 10;
|
|
if (restartAttempts >= maxAttempts) {
|
|
NSLog(@"❌ Maximum restart attempts (%lu) reached, giving up",
|
|
(unsigned long)maxAttempts);
|
|
return;
|
|
}
|
|
|
|
restartAttempts++;
|
|
|
|
NSTimeInterval delay = MIN(pow(2, restartAttempts - 1), 60.0);
|
|
|
|
NSLog(@"⏰ Scheduling restart attempt %lu in %.1f seconds",
|
|
(unsigned long)restartAttempts, delay);
|
|
|
|
restartTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, serverQueue);
|
|
|
|
dispatch_source_set_timer(restartTimer,
|
|
dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC),
|
|
DISPATCH_TIME_FOREVER,
|
|
0.1 * NSEC_PER_SEC);
|
|
|
|
dispatch_source_set_event_handler(restartTimer, ^{
|
|
[self _startInternal];
|
|
});
|
|
|
|
dispatch_resume(restartTimer);
|
|
}
|
|
|
|
- (void)_tryFallbackPorts {
|
|
NSLog(@"🔍 Searching for available fallback port...");
|
|
|
|
for (uint16_t port = FALLBACK_PORT_START; port <= FALLBACK_PORT_END; port++) {
|
|
// ⭐️ 检查端口是否真的可用
|
|
if ([self _isPortAvailable:port]) {
|
|
currentPort = port;
|
|
NSLog(@"✅ Found available port: %d", port);
|
|
[self _startInternal];
|
|
return;
|
|
}
|
|
}
|
|
|
|
NSLog(@"❌ No available fallback ports found in range %d-%d",
|
|
FALLBACK_PORT_START, FALLBACK_PORT_END);
|
|
[self _scheduleRestartWithBackoff];
|
|
}
|
|
|
|
// ⭐️ 检查端口是否可用(改进版)
|
|
- (BOOL)_isPortAvailable:(uint16_t)port {
|
|
int testSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (testSocket < 0) {
|
|
NSLog(@"⚠️ Cannot create test socket");
|
|
return NO;
|
|
}
|
|
|
|
// 设置SO_REUSEADDR
|
|
int reuseAddr = 1;
|
|
setsockopt(testSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
int result = bind(testSocket, (struct sockaddr *)&addr, sizeof(addr));
|
|
close(testSocket);
|
|
|
|
if (result == 0) {
|
|
return YES;
|
|
} else {
|
|
if (errno == EADDRINUSE) {
|
|
NSLog(@"⚠️ Port %d is in use", port);
|
|
}
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
- (void)scheduleRestart {
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)),
|
|
serverQueue, ^{
|
|
[self start];
|
|
});
|
|
}
|
|
|
|
#pragma mark - GCDAsyncUdpSocket Delegate
|
|
|
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
|
|
NSLog(@"✅ Connected to client");
|
|
}
|
|
|
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
|
|
didReceiveData:(NSData *)data
|
|
fromAddress:(NSData *)address
|
|
withFilterContext:(id)filterContext {
|
|
@autoreleasepool {
|
|
// ⭐️ 检查数据大小,防止超大包
|
|
if (data.length > 65507) { // UDP最大包大小
|
|
NSLog(@"⚠️ Received oversized packet: %lu bytes", (unsigned long)data.length);
|
|
return;
|
|
}
|
|
|
|
NSString *datastr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
if (!datastr) {
|
|
NSLog(@"⚠️ Failed to decode received data (length: %lu)", (unsigned long)data.length);
|
|
return;
|
|
}
|
|
|
|
NSLog(@"📨 UDP Request from %@: %@",
|
|
[self _addressToString:address],
|
|
[datastr substringToIndex:MIN(100, datastr.length)]);
|
|
|
|
// ⭐️ 异步处理请求,避免阻塞接收
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
UDPHandler *handle = [UDPHandler sharedInstance];
|
|
NSString *res = [handle handle:datastr];
|
|
|
|
if (res) {
|
|
[self _sendResponse:res toAddress:address fromSocket:sock];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// ⭐️ 发送响应(带超时和重试逻辑)
|
|
- (void)_sendResponse:(NSString *)response
|
|
toAddress:(NSData *)address
|
|
fromSocket:(GCDAsyncUdpSocket *)sock {
|
|
dispatch_async(serverQueue, ^{
|
|
if (!sock || sock.isClosed) {
|
|
NSLog(@"⚠️ Cannot send response: socket is closed");
|
|
return;
|
|
}
|
|
|
|
NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding];
|
|
if (!responseData) {
|
|
NSLog(@"⚠️ Failed to encode response");
|
|
return;
|
|
}
|
|
|
|
// ⭐️ 检查响应大小
|
|
if (responseData.length > 65507) {
|
|
NSLog(@"⚠️ Response too large: %lu bytes (max 65507)",
|
|
(unsigned long)responseData.length);
|
|
return;
|
|
}
|
|
|
|
long tag = ++self->_currentTag;
|
|
|
|
// 保存待发送数据
|
|
self->_pendingSends[@(tag)] = @{
|
|
@"response": response,
|
|
@"address": address,
|
|
@"timestamp": @([[NSDate date] timeIntervalSince1970])
|
|
};
|
|
|
|
NSLog(@"📤 Sending response (tag: %ld, size: %lu bytes)",
|
|
tag, (unsigned long)responseData.length);
|
|
|
|
[sock sendData:responseData
|
|
toAddress:address
|
|
withTimeout:SEND_TIMEOUT
|
|
tag:tag];
|
|
|
|
// ⭐️ 设置超时检查
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((SEND_TIMEOUT + 1.0) * NSEC_PER_SEC)),
|
|
self->serverQueue, ^{
|
|
[self _checkSendTimeout:tag];
|
|
});
|
|
});
|
|
}
|
|
|
|
// ⭐️ 检查发送超时
|
|
- (void)_checkSendTimeout:(long)tag {
|
|
NSDictionary *pendingData = _pendingSends[@(tag)];
|
|
if (pendingData) {
|
|
NSTimeInterval timestamp = [pendingData[@"timestamp"] doubleValue];
|
|
NSTimeInterval elapsed = [[NSDate date] timeIntervalSince1970] - timestamp;
|
|
|
|
NSLog(@"⏱️ Send timeout for tag %ld (elapsed: %.1fs)", tag, elapsed);
|
|
[_pendingSends removeObjectForKey:@(tag)];
|
|
}
|
|
}
|
|
|
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {
|
|
NSLog(@"❌ Did not connect: %@", error);
|
|
}
|
|
|
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
|
|
NSLog(@"✅ Data sent successfully (tag: %ld)", tag);
|
|
[_pendingSends removeObjectForKey:@(tag)];
|
|
}
|
|
|
|
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
|
|
didNotSendDataWithTag:(long)tag
|
|
dueToError:(NSError *)error {
|
|
NSLog(@"❌ Failed to send data (tag: %ld): %@", tag, error);
|
|
|
|
[_pendingSends removeObjectForKey:@(tag)];
|
|
|
|
// ⭐️ 处理各种发送错误
|
|
if (error.code == 55) { // ENOBUFS
|
|
NSLog(@"⚠️ Buffer full (ENOBUFS) - system may be overloaded");
|
|
} else if (error.code == 57) { // ENOTCONN
|
|
NSLog(@"⚠️ Socket disconnected (ENOTCONN)");
|
|
[self _startInternal];
|
|
} else if (error.code == 64) { // EHOSTDOWN
|
|
NSLog(@"⚠️ Host is down (EHOSTDOWN)");
|
|
} else if (error.code == 65) { // EHOSTUNREACH
|
|
NSLog(@"⚠️ Host unreachable (EHOSTUNREACH)");
|
|
}
|
|
}
|
|
|
|
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error {
|
|
NSLog(@"⚠️ Socket closed. Error: %@", error);
|
|
|
|
if (sock == serverSocket) {
|
|
serverSocket = nil;
|
|
}
|
|
|
|
if (error) {
|
|
NSLog(@"❌ Unexpected closure, scheduling restart");
|
|
[self scheduleRestart];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Utility Methods
|
|
|
|
// ⭐️ 将地址转换为可读字符串
|
|
- (NSString *)_addressToString:(NSData *)addressData {
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)addressData.bytes;
|
|
char ipStr[INET_ADDRSTRLEN];
|
|
inet_ntop(AF_INET, &addr->sin_addr, ipStr, sizeof(ipStr));
|
|
return [NSString stringWithFormat:@"%s:%d", ipStr, ntohs(addr->sin_port)];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self _cancelRestartTimer];
|
|
[self _stopInternal];
|
|
}
|
|
|
|
@end |