1

我有一个大致遵循以下步骤的 iOS 应用程序:

  1. 打开一个监听套接字。
  2. 接受单个客户端连接。
  3. 执行与客户端的数据交换。
  4. 当它接收到“resign active”事件时,它会关闭并释放与客户端和服务器套接字关联的所有资源(即,使所有运行循环源、读/写流和套接字本身无效并释放)。
  5. 恢复活动后,它会恢复侦听套接字以继续通信(在 iOS 应用程序在步骤 #4 中退出活动后,客户端将继续尝试重新连接,直到能够重新连接)。

每当客户端和服务器之间确实发生连接时,我在第 5 步之后看到的是应用程序恢复但无法重新打开服务器套接字进行侦听。换句话说,即使在第 5 步中释放了所有内容,应用程序也无法重新绑定和侦听套接字地址。更糟糕的是,在尝试重新设置侦听套接字时,CFSocket API 调用中没有检测到错误。

另一方面,如果 iOS 应用程序退出活动并再次恢复,而之前没有接收到任何连接,则客户端可以只连接一次,直到应用程序退出并再次恢复,在这种情况下,可以观察到上述相同的行为.

可以在以下存储库中找到说明此问题的示例最小应用程序:

https://github.com/dpereira/cfsocket_reopen_bug

最相关的来源是:

#import "AppDelegate.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

static void _handleConnect(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* data, void* info)
{
    NSLog(@"Connected ...");
    close(*(CFSocketNativeHandle*)data);
    NSLog(@"Closed ...");
}

@interface AppDelegate ()

@end

@implementation AppDelegate {
    CFRunLoopSourceRef _source;
    CFSocketRef _serverSocket;
    CFRunLoopRef _socketRunLoop;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    
    CFRunLoopRemoveSource(self->_socketRunLoop, self->_source, kCFRunLoopCommonModes);
    CFRunLoopSourceInvalidate(self->_source);
    CFRelease(self->_source);
    self->_source = nil;
    
    CFSocketInvalidate(self->_serverSocket);
    CFRelease(self->_serverSocket);
    self->_serverSocket = nil;
    
    CFRunLoopStop(self->_socketRunLoop);
    
    NSLog(@"RELASED SUCCESSFULLY!");
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    CFSocketContext ctx = {0, (__bridge void*)self, NULL, NULL, NULL};
    self->_serverSocket = CFSocketCreate(kCFAllocatorDefault,
                                        PF_INET,
                                        SOCK_STREAM,
                                        IPPROTO_TCP,
                                        kCFSocketAcceptCallBack, _handleConnect, &ctx);
    
    NSLog(@"Socket created %u", self->_serverSocket != NULL);
    
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_len = sizeof(sin);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(30000);
    sin.sin_addr.s_addr= INADDR_ANY;
    
    CFDataRef sincfd = CFDataCreate(kCFAllocatorDefault,
                                    (UInt8 *)&sin,
                                    sizeof(sin));
    CFSocketSetAddress(self->_serverSocket, sincfd);
    CFRelease(sincfd);
    

    self->_source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,
                                               self->_serverSocket,
                                               0);
    
    NSLog(@"Created source %u", self->_source != NULL);
    
    self->_socketRunLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(self->_socketRunLoop,
                       self->_source,
                       kCFRunLoopCommonModes);
    
    NSLog(@"Registered into run loop");
    NSLog(@"Socket is %s", CFSocketIsValid(self->_serverSocket) ? "valid" : "invalid");
    NSLog(@"Source is %s", CFRunLoopSourceIsValid(self->_source) ? "valid" : "invalid");
}

@end

成熟的应用程序位于:https ://github.com/dpereira/conflux

套接字(和相关资源)的设置/拆卸是否有问题?

4

1 回答 1

0

这里的问题是侦听套接字进入 TIME_WAIT 并且在该状态下无法再次绑定。

即使 CFSocket API 没有返回错误,如果在使用 POSIX 套接字时发生同样的情况,在尝试重新绑定套接字时也会发生错误。

解决方案是在重新绑定套接字以进行侦听之前简单地为套接字设置 SO_REUSEADDR 选项。

于 2015-06-14T22:18:01.240 回答