Setting Up Socket Streams

You can use the CFStream API to establish a socket connection and, with the stream object (or objects) created as a result, send data to and receive data from a remote host. You can also configure the connection for security.

Basic Procedure

The NSStream class does not support connecting to a remote host on iOS. CFStream does support this behavior, however, and once you have created your streams with the CFStream API, you can take advantage of the toll-free bridge between CFStream and NSStream to cast your CFStreams to NSStreams. Just call the CFStreamCreatePairWithSocketToHost function, providing a host name and a port number, to receive both a CFReadStreamRef and a CFWriteStreamRef for the given host. You can then cast these objects to an NSInputStream and an NSOutputStream and proceed.

Listing 1 illustrates the use of CFStreamCreatePairWithSocketToHost. This example shows the creation of both a CFReadStreamRef object and a CFWriteStreamRef object. If you want to receive only one of these objects, just specify NULL as the parameter value for the unwanted object.

Listing 1  Setting up a network socket stream

- (IBAction)searchForSite:(id)sender
{
    NSString *urlStr = [sender stringValue];
    if (![urlStr isEqualToString:@""]) {
        NSURL *website = [NSURL URLWithString:urlStr];
        if (!website) {
            NSLog(@"%@ is not a valid URL");
            return;
        }
 
        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
        CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 80, &readStream, &writeStream);
 
        NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
        NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
        [inputStream setDelegate:self];
        [outputStream setDelegate:self];
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [inputStream open];
        [outputStream open];
 
        /* Store a reference to the input and output streams so that
           they don't go away.... */
        ...
    }
}

If you pass in invalid parameters, one or both of the requested CFReadStreamRef and CFWriteStreamRef objects are NULL. Once you have cast the CFStreams to NSStreams, set the delegate, schedule the stream on a run loop, and open the stream as usual. The delegate should begin to receive stream-event messages (stream:handleEvent:). See Reading From Input Streams and Writing To Output Streams for more information.

Securing and Configuring the Connection

Before you open a stream object, you might want to set security and other features for the connection to the remote host (which might be, for example, an HTTPS server). NSStream defines properties that affect the security of TCP/IP socket connections in two ways:

For SSL security, NSStream defines various security-level properties (for example, NSStreamSocketSecurityLevelSSLv2). You set these properties by sending setProperty:forKey: to the stream object using the key NSStreamSocketSecurityLevelKey, as in this sample message:

[inputStream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];

You must set the property before you open the stream. Once it opens, it goes through a handshake protocol to find out what level of SSL security the other side of the connection is using. If the security level is not compatible with the specified property, the stream object generates an error event. However, if you request a negotiated security level (NSStreamSocketSecurityLevelNegotiatedSSL), the security level becomes the highest that both sides of the connection can implement. Still, if you try to set an SSL security level when the remote host is not secure, an error is generated.

To configure a SOCKS proxy server for a connection, you need to construct a dictionary with keys of the form NSStreamSOCKSProxyNameKey (for example, NSStreamSOCKSProxyHostKey). The value of each key is the SOCKS proxy setting that Name refers to. Then using setProperty:forKey:, set the dictionary as the value of the NSStreamSOCKSProxyConfigurationKey.

Initiating an HTTP Request

If you are opening a connection to an HTTP server (that is, a website), then you may have to initiate a transaction with that server by sending it an HTTP request. A good time to make this request is when the delegate of the NSOutputStream object receives a NSStreamEventHasSpaceAvailable event via a stream:handleEvent: message. Listing 2 shows the delegate creating an HTTP GET request and writing it to the output stream, after which it immediately closes the stream object.

Listing 2  Making an HTTP GET request

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
    NSLog(@"stream:handleEvent: is invoked...");
 
    switch(eventCode) {
        case NSStreamEventHasSpaceAvailable:
        {
            if (stream == oStream) {
                NSString * str = [NSString stringWithFormat:
                    @"GET / HTTP/1.0\r\n\r\n"];
                const uint8_t * rawstring =
                    (const uint8_t *)[str UTF8String];
                [oStream write:rawstring maxLength:strlen(rawstring)];
                [oStream close];
            }
            break;
        }
        // continued ...
    }
}

For More Information

To learn more about using streams for networking, read Networking Overview.