objective-cnetwork-programmingclient-servernsstreamcfsocket

How to fix naive server implementation with CFSocket to allow multiple connections


I have been studying the bonjour/NSStream sample code from lecture #17 of Stanford's CS193p course (iOS programming) on iTunes U from the winter of 2010. The example code is available here.

In a nut shell, the sample code creates a socket and binds to port 0 so that it will be given a free port. It then publishes a service with that port using NSNetService (bonjour). An NSNetServiceBrowser is also started when the app starts up. Available services are placed in a UITableView. When a cell is selected, the corresponding service is resolved, an NSOutputStream is created, and data can be sent.

This is a naive implementation because connections are rejected if a connection already exists. My question is, what is the proper way to handle multiple connections? Once multiple clients are connected to the server, how does the server distinguish between them? i.e. How can data be sent specifically to one client and not the others?

- (void) _acceptConnection:(int)fd
{
    int     junk;

// If we already have a connection, reject this new one.  This is one of the 
// big simplifying assumptions in this code.  A real server should handle 
// multiple simultaneous connections.

    if ( self.isReceiving ) {
        junk = close(fd);
        assert(junk == 0);
    } else {
        [self _startReceive:fd];
    }
}


// Called by CFSocket when someone connects to our listening socket.  
// This implementation just bounces the request up to Objective-C.
static void AcceptCallback(CFSocketRef s, 
                           CFSocketCallBackType type, 
                           CFDataRef address, 
                           const void *data, 
                           void *info)

{
    ReceiveServer *  obj;

    assert(type == kCFSocketAcceptCallBack);

    assert(data != NULL);

    obj = (ReceiveServer *) info;
    assert(obj != nil);

    assert(s == obj->_listeningSocket);


    [obj _acceptConnection:*(int *)data];
}

Solution

  • I'm not specifically familiar with that course or sample code, but: separate out the code for handling a connection into a different class from the code which accepts new connections. So, in the posted code, you'd move the -startReceive: method and everything which it calls to another class.

    Then, each time a connection is accepted, create an instance of that other class. That instance will be responsible for processing all of the communication on that connection. It will be given the information about the connection (mainly the fd) during initialization. The main controller object of the server could hold those instances in an array.

    Where things go from here will depend on what your server actually does. At the very least, your connection objects will need to inform the main controller object when they are closed, so the main controller can remove them from the array. You can use notifications or the delegate pattern for that.