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