我有一个iOS应用程序,大致遵循以下步骤:
- 打开监听套接字。
- 接受单个客户端连接。
- 执行与客户端的数据交换。
- 当它接收到"resign active"事件时,它关闭并释放与客户端和服务器套接字相关的所有资源(即使所有运行循环源,读/写流和套接字本身无效并释放)。
- 在恢复活动后,它会恢复侦听套接字以继续通信(客户端将继续尝试重新连接,直到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
在套接字(和相关资源)的设置/拆除中是否有问题?
这里的问题是侦听套接字正在进入TIME_WAIT状态,并且在该状态下无法再次绑定。
即使CFSocket API没有返回错误,如果在使用POSIX套接字时发生相同的情况,则在尝试重新绑定套接字时发生错误。
解决方案是在重新绑定套接字以侦听之前简单地为套接字设置SO_REUSEADDR选项。