Search code examples
cocoa-touchsocketsgcdasyncsocket

GCDAsyncSocket unable to acceptOnPort more than once even after disconnecting it


I want to be able to close a socket that currently listens to a port, and then come back to it and re-establish a listen on the port. I am unable to do that because the second acceptOnPort call on a different server listen socket always ends up with an error (address already in use). How can I close out a listening socket and re-establish a new one?


Solution

  • See issue 146 which I just added.

    GCDAsyncSocket will never deallocate because the dispatch_source_set_event_handler holds a reference to a block which holds a reference to the GCDAsyncSocket self.

    This results in the inability to close and then reopen a GCDAsyncSocket listener as the address is already in use.

    This can be resolved by changing the reference to a weak reference. Before the dispatch_source_set_event_handler, add the line:

    __weak GCDAsyncSocket* weakSelf = self;

    and then using weakSelf instead of self to call doAccept.

    while ([weakSelf doAccept:socketFD] && (++i < numPendingConnections));

    You need to repeat this twice, once for ipv4 and once for ipv6.

    At this point, you'll discover that it is a good thing GCDAsyncSocket is never dealloced because it crashes immediately the dealloc is run. This is because dealloc calls closeWithError, which in turn calls the delegate socketDidDisconnect, passing the GCDAsyncSocket self as a parameter. ARC promptly retains the GCDAsyncSocket which crashes as the GCDAsyncSocket is currently being deallocate.

    This can be resolved by moving the "delegate = nil" to the start of the dealloc, which you may as well do as it is impossible to call the delegate safely at that point (well, it is impossible if you want to be able to pass the GCDAsyncSocket, which you can no longer do). An alternative would be to call socketDidDisconnect:nil in this case.

    Either way, it means that socketDidDisconnect will not be called, or alternatively, will not be called with the appropriate GCDAsyncSocket as a parameter, which may break the API contract, but is unavoidable at this point.

    A better API would be to have some sort of "Kill" method call to kill the GCDAsyncSocket before the dealloc happens.