You can use the NSStream class 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.
<!-- This template is being used for both PDF and HTML. --><!-- TopicBook.pm uses this template for its miniTOCs, but needs a different title. -->
Basic Procedure
Securing and Configuring the Connection
Initiating an HTTP Request
Setting up a socket connection is easy. Just send the NSStream class a getStreamsToHost:port:inputStream:outputStream:
message and you will receive back an object representing an input stream from the remote host or an output stream to the remote host—or both input- and output-stream objects. The getStreamsToHost:port:inputStream:outputStream:
class method merely requires you to provide an NSHost object (identifying the remote host) and a port number.
Listing 1 illustrates the use of getStreamsToHost:port:inputStream:outputStream:
. This example shows the creation of both an NSInputStream object and an NSOutputStream object. If you want to receive only one of these objects, just specify nil
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:@""]) { [searchField setEnabled:NO]; NSURL *website = [NSURL URLWithString:urlStr]; if (!website) { NSLog(@"%@ is not a valid URL"); return; } NSHost *host = [NSHost hostWithName:[website host]]; // iStream and oStream are instance variables [NSStream getStreamsToHost:host port:80 inputStream:&iStream outputStream:&oStream]; [iStream retain]; [oStream retain]; [iStream setDelegate:self]; [oStream setDelegate:self]; [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [iStream open]; [oStream open]; } }
Because the stream objects you receive back from getStreamsToHost:port:inputStream:outputStream:
are autoreleased, be sure to retain them right away. If the socket connection fails, then one or both of the requested NSInputStream and NSOutputStream objects are nil
. Then, as usual, set the delegate, schedule the stream on a run loop, and open the stream. The delegate should begin to receive stream-event messages (stream:handleEvent:
). See “Reading From Input Streams” and “Writing To Output Streams” for more information.
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:
Secure Socket Layer (SSL).
A security protocol using digital certificates to provide data encryption, server authentication, message integrity, and (optionally) client authentication for TCP/IP connections.
SOCKS proxy server.
A server that sits between a client application and a real server over a TCP/IP connection. It intercepts requests to the real server and, if it cannot fulfill them from a cache of recently requested files, forwards them to the real server. SOCKS proxy servers help improve performance over a network and can also be used to filter requests.
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:
[iStream 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 NSStreamSOCKSProxy
NameKey
(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
.
If you know the proxy-server settings, you can construct the dictionary yourself. But an easier way to get a dictionary of current proxy settings is to use the System Configuration framework. To use this API in your program, add SystemConfiguration.framework
to your project and import the <SystemConfiguration/SystemConfiguration.h>
header file. Next, as shown in Listing 2, call the function SCDynamicStoreCopyProxies
and be sure to cast the returned CFDictionary value to an NSDictionary object. Then use this dictionary to set the NSStreamSOCKSProxyConfigurationKey
property.
Listing 2 Setting a stream to the current SOCKS proxy settings
// ... NSDictionary *proxyDict = (NSDictionary *)SCDynamicStoreCopyProxies(NULL); [oStream setProperty:proxyDict forKey:NSStreamSOCKSProxyConfigurationKey]; // ...
For a detailed example of using the System Configuration API to get SOCKS proxy settings, see Technical Q&A QA1234, “Accessing HTTPS Proxy Settings.”
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 3 shows the delegate creating an HTTP GET request and writing it to the output stream, after which it immediately closes the stream object.
Listing 3 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 ... } }