Using Sockets and Socket Streams

This article explains how to work with sockets and socket streams at various levels, from POSIX through Foundation.

At almost every level of networking, software can be divided into two categories: clients (programs that connect to other apps) and services (programs that other apps connect to). At a high level, these lines are clear. Most programs written using high-level APIs are purely clients. At a lower level, however, the lines are often blurry.

Socket and stream programming generally falls into one of the following broad categories:

This chapter is divided into sections based on the above tasks:

Choosing an API Family

The API you choose for socket-based connections depends on whether you are making a connection to another host or receiving a connection from another host. It also depends on whether you are using TCP or some other protocol. Here are a few factors to consider:

Writing a TCP-Based Client

The way you make an outgoing connection depends on what programming language you are using, on the type of connection (TCP, UDP, and so forth), and on whether you are trying to share code with other (non-Mac, non-iOS) platforms.

The subsections below describe the use of NSStream. Except where noted, the CFStream API has functions with similar names, and behaves similarly.

To learn more about the POSIX socket API, read the UNIX Socket FAQ at http://developerweb.net/.

Establishing a Connection

As a rule, the recommended way to establish a TCP connection to a remote host is with streams. Streams automatically handle many of the challenges that TCP connections present. For example, streams provide the ability to connect by hostname, and in iOS, they automatically activate a device’s cellular modem or on-demand VPN when needed (unlike CFSocket or BSD sockets). Streams are also a more Cocoa-like networking interface than lower-level protocols, behaving in a way that is largely compatible with the Cocoa file stream APIs.

The way you obtain input and output streams for a host depends on whether you used service discovery to discover the host:

After you have obtained your input and output streams, you should retain them immediately if you are not using automatic reference counting. Then cast them to NSInputStream and NSOutputStream objects, set their delegate objects (which should conform to the NSStreamDelegate protocol), schedule them on the current run loop, and call their open methods.

Handling Events

When the stream:handleEvent: method is called on the NSOutputStream object’s delegate and the streamEvent parameter’s value is NSStreamEventHasSpaceAvailable, call write:maxLength: to send data. This method returns the number of bytes written or a negative number on error. If fewer bytes were written than you tried to send, you must queue up the remaining data and send it after the delegate method gets called again with an NSStreamEventHasSpaceAvailable event. If an error occurs, you should call streamError to find out what went wrong.

When the stream:handleEvent: method is called on your NSInputStream object’s delegate and the streamEvent parameter’s value is NSStreamEventHasBytesAvailable, your input stream has received data that you can read with the read:maxLength: method. This method returns the number of bytes read, or a negative number on error.

If fewer bytes were read than you need, you must queue the data and wait until you receive another stream event with additional data. If an error occurs, you should call streamError to find out what went wrong.

If the other end of the connection closes the connection:

  • Your connection delegate’s stream:handleEvent: method is called with streamEvent set to NSStreamEventHasBytesAvailable. When you read from that stream, you get a length of zero (0).

  • Your connection delegate’s stream:handleEvent: method is called with streamEvent set to NSStreamEventEndEncountered.

When either of these two events occurs, the delegate method is responsible for detecting the end-of-file condition and cleaning up.

Closing the Connection

To close your connection, unschedule it from the run loop, set the connection’s delegate to nil (the delegate is unretained), close both of the associated streams with the close method, and then release the streams themselves (if you are not using ARC) or set them to nil (if you are). By default, this closes the underlying socket connection. There are two situations in which you must close it yourself, however:

For More Information

To learn more, read “Setting Up Socket Streams” in Stream Programming Guide, Using NSStreams For A TCP Connection Without NSHost, or see the SimpleNetworkStreams and RemoteCurrency sample code projects.

Writing a TCP-Based Server

As mentioned previously, a server and a client are similar once the connection is established. The main difference is that clients make outgoing connections, whereas servers create a listening socket (sometimes listen socket)—a socket that listens for incoming connections—then accept connections on that socket. After that, each resulting connection behaves just like a connection you might make in a client.

The API you should choose for your server depends primarily on whether you are trying to share the code with other (non-Mac, non-iOS) platforms. There are only two APIs that provide the ability to listen for incoming network connections: the Core Foundation socket API and the POSIX (BSD) socket API. Higher-level APIs cannot be used for accepting incoming connections.

The following sections describe how to use these APIs to listen for incoming connections.

Listening with Core Foundation

To use Core Foundation APIs to listen for incoming connections, you must do the following:

  1. Add appropriate includes:

    #include <CoreFoundation/CoreFoundation.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
  2. Create socket objects (returned as a CFSocketRef object) with the CFSocketCreate or CFSocketCreateWithNative function. Specify kCFSocketAcceptCallBack as the callBackTypes parameter value. Provide a pointer to a CFSocketCallBack callback function as the callout parameter value.

    CFSocketRef myipv4cfsock = CFSocketCreate(
        kCFAllocatorDefault,
        PF_INET,
        SOCK_STREAM,
        IPPROTO_TCP,
        kCFSocketAcceptCallBack, handleConnect, NULL);
    CFSocketRef myipv6cfsock = CFSocketCreate(
        kCFAllocatorDefault,
        PF_INET6,
        SOCK_STREAM,
        IPPROTO_TCP,
        kCFSocketAcceptCallBack, handleConnect, NULL);
  3. Bind a socket with the CFSocketSetAddress function. Provide a CFData object containing a sockaddr struct that specifies information about the desired port and family.

    struct sockaddr_in sin;
     
    memset(&sin, 0, sizeof(sin));
    sin.sin_len = sizeof(sin);
    sin.sin_family = AF_INET; /* Address family */
    sin.sin_port = htons(0); /* Or a specific port */
    sin.sin_addr.s_addr= INADDR_ANY;
     
    CFDataRef sincfd = CFDataCreate(
        kCFAllocatorDefault,
        (UInt8 *)&sin,
        sizeof(sin));
     
    CFSocketSetAddress(myipv4cfsock, sincfd);
    CFRelease(sincfd);
     
    struct sockaddr_in6 sin6;
     
    memset(&sin6, 0, sizeof(sin6));
    sin6.sin6_len = sizeof(sin6);
    sin6.sin6_family = AF_INET6; /* Address family */
    sin6.sin6_port = htons(0); /* Or a specific port */
    sin6.sin6_addr = in6addr_any;
     
    CFDataRef sin6cfd = CFDataCreate(
        kCFAllocatorDefault,
        (UInt8 *)&sin6,
        sizeof(sin6));
     
    CFSocketSetAddress(myipv6cfsock, sin6cfd);
    CFRelease(sin6cfd);
  4. Begin listening on a socket by adding the socket to a run loop.

    Create a run-loop source for a socket with the CFSocketCreateRunLoopSource function. Then, add the socket to a run loop by providing its run-loop source to the CFRunLoopAddSource function.

    CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource(
        kCFAllocatorDefault,
        myipv4cfsock,
        0);
     
    CFRunLoopAddSource(
        CFRunLoopGetCurrent(),
        socketsource,
        kCFRunLoopDefaultMode);
     
    CFRunLoopSourceRef socketsource6 = CFSocketCreateRunLoopSource(
        kCFAllocatorDefault,
        myipv6cfsock,
        0);
     
    CFRunLoopAddSource(
        CFRunLoopGetCurrent(),
        socketsource6,
        kCFRunLoopDefaultMode);

After this, you can access the underlying BSD socket descriptor with the CFSocketGetNative function.

When you are through with the socket, you must close it by calling CFSocketInvalidate.

In your listening socket’s callback function (handleConnect in this case), you should check to make sure the value of the callbackType parameter is kCFSocketAcceptCallBack, which means that a new connection has been accepted. In this case, the data parameter of the callback is a pointer to a CFSocketNativeHandle value (an integer socket number) representing the socket.

To handle the new incoming connections, you can use the CFStream, NSStream, or CFSocket APIs. The stream-based APIs are strongly recommended.

To do this:

  1. Create read and write streams for the socket with the CFStreamCreatePairWithSocket function.

  2. Cast the streams to an NSInputStream object and an NSOutputStream object if you are working in Cocoa.

  3. Use the streams as described in “Writing a TCP-Based Client.”

For more information, see CFSocket Reference. For sample code, see the RemoteCurrency and WiTap sample code projects.

Listening with POSIX Socket APIs

POSIX networking is fairly similar to the CFSocket API, except that you have to write your own run-loop-handling code.

Here are the basic steps for creating a POSIX-level server:

  1. Create a socket by calling socket. For example:

    int ipv4_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    int ipv6_socket = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
  2. Bind it to a port.

    • If you have a specific port in mind, use that.

    • If you don’t have a specific port in mind, pass zero for the port number, and the operating system will assign you an ephemeral port. (If you are going to advertise your service with Bonjour, you should almost always use an ephemeral port.)

    For example:

        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(sin));
        sin.sin_len = sizeof(sin);
        sin.sin_family = AF_INET; // or AF_INET6 (address family)
        sin.sin_port = htons(0);
        sin.sin_addr.s_addr= INADDR_ANY;
     
        if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
            // Handle the error.
        }
  3. If you are using an ephemeral port, call getsockname to find out what port you are using. You can then register this port with Bonjour. For example:

        socklen_t len = sizeof(sin);
        if (getsockname(listen_sock, (struct sockaddr *)&sin, &len) < 0) {
            // Handle error here
        }
        // You can now get the port number with ntohs(sin.sin_port).
  4. Call listen to begin listening for incoming connections on that port.

The next steps depend on whether you intend to use pure POSIX socket code or a higher level abstraction.

Handling Events with Core Foundation

Call CFSocketCreateWithNative. Then follow the directions in “Listening with Core Foundation,” beginning at step 3.

Handling Events with Grand Central Dispatch

GCD allows you to perform operations asynchronously, and provides an event queue mechanism for determining when to read data from the socket. After creating the listening socket, a GCD-based server should:

  1. Call dispatch_source_create to create a dispatch source for the listening socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type.

  2. Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever a new connection arrives on the socket.

  3. When the listen socket handler is called (upon a new connection), it should:

    • Call accept. This function fills a new sockaddr structure with information about the connection and returns a new socket for that connection.

      If desired, call ntohl(my_sockaddr_obj.sin_addr.s_addr) to determine the client’s IP address.

    • Call dispatch_source_create to create a dispatch source for the client socket, specifying DISPATCH_SOURCE_TYPE_READ as the source type.

    • Call setsockopt to set the SO_NOSIGPIPE flag on the socket.

    • Call dispatch_source_set_event_handler (or dispatch_source_set_event_handler_f and dispatch_set_context) to set a handler that gets called whenever the state of the connection changes.

  4. In the client socket handler, call dispatch_async or dispatch_async_f and pass a block that calls read on the socket to grab any new data, then handle that data appropriately. This block can also send responses by calling write on the socket.

Handling Events with Pure POSIX Code

  1. Create a file descriptor set and add new sockets to that set as new connections come in.

    fd_set incoming_connections;
    memset(&incoming_connections, 0, sizeof(incoming_connections));
  2. If you need to perform actions periodically on your networking thread, construct a timeval structure for the select timeout.

        struct timeval tv;
        tv.tv_sec = 1; /* 1 second timeout */
        tv.tv_usec = 0; /* no microseconds. */

    It is important to choose a timeout that is reasonable. Short timeout values bog down the system by causing your process to run more frequently than is necessary. Unless you are doing something very unusual, your select loop should not wake more than a few times per second, at most, and on iOS, you should try to avoid doing this at all. For alternatives, read “Avoid POSIX Sockets and CFSocket on iOS Where Possible” in Networking Overview.

    If you do not need to perform periodic actions, pass NULL.

  3. Call select in a loop, passing two separate copies of that file descriptor set (created by calling FD_COPY) for the read and write descriptor sets. The select system call modifies these descriptor sets, clearing any descriptors that are not ready for reading or writing.

    For the timeout parameter, pass the timeval structure you created earlier. Although OS X and iOS do not modify this structure, some other operating systems replace this value with the amount of time remaining. Thus, for cross-platform compatibility, you must reset this value each time you call select.

    For the nfds parameter, pass a number that is one higher than the highest-numbered file descriptor that is actually in use.

  4. Read data from sockets, calling FD_ISSET to determine if a given socket has pending data.

    Write data to calling FD_ISSET to determine if a given socket has room for new data.

    Maintain appropriate queues for incoming and outgoing data.

As an alternative to the POSIX select function, the BSD-specific kqueue API can also be used to handle socket events.

For More Information

To learn more about POSIX networking, read the socket, listen, FD_SET, and select manual pages.

Working with Packet-Based Sockets

The recommended way to send and receive UDP packets is by combining the POSIX API and either the CFSocket or GCD APIs. To use these APIs, you must perform the following steps:

  1. Create a socket by calling socket.

  2. Bind the socket by calling bind. Provide a sockaddr struct that specifies information about the desired port and family.

  3. Connect the socket by calling connect (optional).

    Note that a connected UDP socket is not a connection in the purest sense of the word. However, it provides two advantages over an unconnected socket. First, it removes the need to specify the destination address every time you send a new message. Second, your app may receive errors when a packet cannot be delivered. This error delivery is not guaranteed with UDP, however; it is dependent on network conditions that are beyond your app’s control.

From there, you can work with the connection in three ways:

Obtaining the Native Socket Handle for a Socket Stream

Sometimes when working with socket-based streams (NSInputStream, NSOutputStream, CFReadStream, or CFWriteStream), you may need to obtain the underlying socket handle associated with a stream. For example, you might want to find out the IP address and port number for each end of the stream with getsockname and getpeername, or set socket options with setsockopt.

To obtain the native socket handle for an input stream, call the following method:

-(int) socknumForNSInputStream: (NSStream *)stream
{
    int sock = -1;
    NSData *sockObj = [stream propertyForKey:
               (__bridge NSString *)kCFStreamPropertySocketNativeHandle];
    if ([sockObj isKindOfClass:[NSData class]] &&
         ([sockObj length] == sizeof(int)) ) {
        const int *sockptr = (const int *)[sockObj bytes];
        sock = *sockptr;
    }
    return sock;
}

You can do the same thing with an output stream, but you only need to do this with one or the other because the input and output streams for a given connection always share the same underlying native socket.