如何确保服务器端CFSocket在应用程序重新启动后侦听并再次返回活动状态?

时间:2015-05-19 02:24:05

标签: ios iphone sockets cfsocket

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

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

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

1 个答案:

答案 0 :(得分:0)

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

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

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