I have an iOS application that follows roughly the following steps:
Whenever a connection does take place between client and server, what I am seeing after step #5 is that the application resumes without being able to reopening the server socket for listening. In other words, even though everything is released in step #5, the application is not able to rebind and listen at the socket address. What's worse, no errors can be detected in the CFSocket API calls while trying to setup the listening socket back again.
If, on the other hand the iOS application resigns active and resumes back again without previously receiving any connection, the client is then able to connect exactly once, until the application resigns and resumes again, in which case the same behaviour above can then be observed.
An example minimal application that illustrates this issue can be found in the following repository:
https://github.com/dpereira/cfsocket_reopen_bug
The most relevant source is:
#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
The full-blown app resides in: https://github.com/dpereira/conflux
Is there something wrong in the setup/teardown of the sockets (and related resources)?
The issue here is that the listening socket was going into TIME_WAIT and could not be bound to again while in that state.
Even though no errors are returned by the CFSocket API, if the same situation happens when using POSIX sockets an error takes place while trying to re-bind the socket.
The solution was to simply set the SO_REUSEADDR option for the socket just prior to re-binding the socket for listening.