TCP

Modified

Downloads

Echo Server with event handling, written in C for Mac OS X allowing server and following client to be run on one machine.

Echo Client with event handling, written in C and Objective C for iPhone.

iPhone Echo Client using NSStream and NSRunLoop in Objective C by Wisman

Echo Client using AsyncSocket, written in Objective C for Mac OS X.

iPhone Symmetric Client/Server, written in Objective C for iPhone.

Bonjour service discovery for iPhone by Wisman

Bonjour service discovery and client/server iPhone game (WiTap) by Apple

Resources

iPhone SDK

TCP Sockets

CFSocket reference

CFNetwork reference

AsyncSocket Documentation

AsyncSocket site

Overview

Internet protocols such as HTTP, SMTP, FTP, etc. are not appropriate for all applications, custom protocols can be more efficient and functional.

The following presents graded approaches to implementing an echo client/server, leading to an event-driven Mac OS X server and iPhone client.

telnet

telnet is useful for debugging, can play the role of the client, can be run at the command line, connecting to a server given a DNS name or IP address and port number. For example, running a server on port 888 and telnet on the same machine, connect and interact with the server by:

telnet localhost 888

Transport layer

TCP

Designed to provide reliable end-to-end service over unreliable network layer.

Runs on hosts communicating via TCP segments encapsulated as data within an IP packet.

IP is a routed protocol for hosts communicating via fully addressed packets.

TCP connection is full-duplex, point-to-point (only two ends) between two ports appearing as a byte stream to an application similar to reading a file (no implicit message boundaries though a carriage return can be data).

Naturally implements a client/server connection relationship, where the server opens a port and waits for a connection, the client connects to the port.

Berkley Sockets

A standard set of transport primitives used in Berkley UNIX for TCP and widely ported to other systems, including the iPhone and Windows. Microsoft has made significant improvements such as changing the spelling of several functions to promote incompatibility.
 

TCP Socket Primitives
Primitive Meaning
gethostbyname Returns hostent structure given DNS or IP number.
socket Create new communication end point in program
bind Attach a local port address to a socket
listen Open socket to connection requests and define number of queued connections
accept Block execution until a connection request arrives; server
connect Attempt to establish connection (e.g. to a program blocked at an ACCEPT); client
send Send data over a connection (encapsulated within an TCP segment)
receive Receive data over a connection.
close Closes connection.

Blocking Echo Client/Server

The following example of an echo client/server illustrates the relative ease that networked applications can be implemented using the Berkley sockets, at least using Python.

 

Server

  1. Creates socket.
  2. Binds socket to port 8888.
  3. Blocks, waiting for connection at accept.
  4. When connection, blocks waiting for data to be received.
  5. When data received, reads and sends using socket.
  6. Closes connection.

Client

  1. Creates socket.
  2. Connects to localhost on port 8888.
  3. When connected, sends data.
  4. Blocks, waiting for echoed data to be received.
  5. Closes connection.
Blocking Python Echo Server and Client using Berkley Socket Primitives
// Server

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 8888)) s.listen(1) conn, addr = s.accept() data = conn.recv(1024) conn.send(data) conn.close()
// Client

import socket

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('localhost', 8888)) s.send('Hello world') data = s.recv(1024) s.close()
 

 

TCP Sockets

Internet socket addresses are stored in 16-byte structures of the type sockaddr_in defined as follows:

struct sockaddr_in{
     short sin_family;
     unsigned short sin_port;
     struct in_addr sin_addr;
     char sin_zero[8];
};

For Internet applications:

The in_addr structure has the following form:

struct in_addr {
     unsigned long s_addr;
};

Note that the in_addr struct is exactly 4 bytes long, which is the same size as an IP address.

The IP address and port number are always stored in network (big-endian) byte order. For example:

struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(9999)
sin.sin_addr.s_addr = inet_addr("128.227.224.3");

In the above code example, the structure sin holds the IP address 128.227.224.3, and references the port number 9999.

Two utility functions are used to set these values.

In the above code example, the structure sin holds the IP address 128.227.224.3, and references the port number 9999. Two utility functions are used to set these values. The function htons returns the integer argument passed into it in network byte order.

The function inet_addr converts the string argument from a dotted-quad into a 32-bit integer. Its return value is also in network byte order.

Blocking Echo Client/Server

The following example of an echo client/server illustrates the relative ease that networked applications can be implemented using the Berkley sockets. 

For OS X, in XCode create two projects based on the Command Line Utility template and Foundation Tool.

For the iPhone, place the code within a View application.

The server can be run on one machine and client on another. For example, run the server on OS X, determine the IP of the server machine, replacing localhost with the IP.

Server

  1. Creates and binds a server socket on port 888.
  2. Blocks execution while listening for a connection.
  3. When connected, blocks while waiting to receive data.
  4. Sends data received.
  5. Closes connection

Client

  1. Creates a client socket to port 888 on localhost.
  2. Blocks execution while waiting for a connection.
  3. When connected, sends data "Hello world".
  4. Blocks while waiting to receive echoed data.
  5. Prints data received.
  6. Closes connection
Echo Server and Client using Berkley Socket Primitives
// Server
#include <sys/socket.h>
#include <netinet/in.h>
 
int main (int argc, const char * argv[]) {
 
  char                         buffer[128];
  int                           sinlen;
  struct sockaddr_in    sin;
  int                           s, h;
       
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = htons(888);   // Port 888
                                         // SOCK_STREAM is TCP
  s = socket(AF_INET, SOCK_STREAM,0);
                                        // Bind socket to local port
  bind(s,(struct sockaddr*)&sin,sizeof(sin));
                                        // Listen for 1 connection
  listen(s,1);
  sinlen = sizeof(sin);     
                                        // 1. Block for connection
  h=accept(s,(struct sockaddr*)&sin,&sinlen );
                                        // 2. Block for receive
  recv(h,buffer,sizeof(buffer),0);
                                        // 3. Echo received data
  send(h,buffer,strlen(buffer),0);
   
  close(h);
 
  return 0;
}
// Client
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
 
int main (int argc, const char * argv[]) {

  char                         buffer[128]= "Hello world";
  struct sockaddr_in      sin;
  struct hostent           *host;
  int                           s;
       
  host = gethostbyname("localhost"); 
       
  memcpy(&(sin.sin_addr), host->h_addr,host->h_length);
  sin.sin_family = host->h_addrtype;
  sin.sin_port = htons(888);
                                               // Create socket port 888
  s = socket(AF_INET, SOCK_STREAM,0);
                                               // 1. Block for server accept
  connect(s,  (struct sockaddr*)&sin,sizeof(sin));    
                                               // 2. Send "Hello world"      
  send(s,buffer,strlen(buffer)+1,0);  
                                               // 3. Block for receive
  recv(s,buffer,sizeof(buffer),0);
                                               // Print received data
  NSLog(@"Received %s\n",buffer);
       
  close(s);
 
  return 0;
}

 

Non-blocking Echo Client/Server

Blocking calls to BSD recv are fine for programs running in the background (e.g. Web server) but are intolerable in an interactive environment.

Several solutions are possible:

  1. Create a thread for the blocking accept and rcv calls. The thread would notify the application when connected or data was available through a delegate callback.
  2. Use CFSocket which creates a thread and notifies the application.

Server

  1. Constructs a BSD server socket as before through calling the listen function.
  2. Creates a CFSocket, using the BSD native server socket, with a CFRunLoop (i.e. thread) to accept and notify our method acceptConnection when connection is made.
  3. Starts the CFRunLoop, the server is waiting for a connection.
  4. When connection is made, acceptConnection is called, which creates a CFSocket for sending and receiving data, with a CFRunLoop (i.e. thread) to notify our method receiveData when data is received.
  5. Starts the CFRunLoop, the server is waiting for data.
  6. When data is received from a client, receiveData is called, the data is displayed and echoed back to the client.

Client

  1. Creates a CFSocket with a callback to notify our method receiveData when data is received.
  2. Connects to the server via the socket.
  3. Sends data out the socket.
  4. Creates a CFRunLoop to wait for receive data on the socket.
  5. Starts the CFRunLoop, the client is waiting for socket data.
  6. When data is received from the server, receiveData is called, the data is displayed.

Points of note:

  1. The server creates a new socket and starts a new CFRunLoop for each connection, it can handle multiple clients.
  2. The function call CFRunLoopRun() does not return. Not a problem in event driven applications but any following statements are not reached.
  3. This is pure C code, no Objective C to be found.
Non-blocking Echo Client/Server using Berkley Socket Primitives and CFSocket
// Server
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
void receiveData(CFSocketRef s,
                         CFSocketCallBackType type,
                         CFDataRef address,
                         const void *data,
                         void *info) 
{
    CFDataRef df = (CFDataRef) data;
    int len = CFDataGetLength(df);
    if(len <= 0) return;
   
    UInt8 buffer[len];
    CFRange range = CFRangeMake(0,len);
    NSLog(@"Server received %d bytes from socket %d\n",
                 len, CFSocketGetNative(s));
    CFDataGetBytes(df, range, buffer);
    NSLog(@"Server received: %s\n", buffer);
    NSLog(@"As UInt8 coding: %@", df);
   
    CFSocketSendData(s, address, df, 0);    // Echo
}
 
void acceptConnection(CFSocketRef s,
                                  CFSocketCallBackType type,
                                  CFDataRef address,
                                  const void *data,
                                  void *info) 
{
    CFSocketNativeHandle csock =
                  *(CFSocketNativeHandle *)data;
    CFSocketRef sn;
    CFRunLoopSourceRef source;
   
    NSLog(@"Socket %d received connection socket %d\n",
                 CFSocketGetNative(s), csock);
    sn = CFSocketCreateWithNative(NULL, csock,
                                                 kCFSocketDataCallBack,
                                                 receiveData,
                                                 NULL);
    source = CFSocketCreateRunLoopSource(NULL, sn, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source,
                                  kCFRunLoopDefaultMode);
    CFRelease(source);
    CFRelease(sn);
}
 
int main ()
{

    struct sockaddr_in sin;
    int sock, yes = 1;
    CFSocketRef s;
    CFRunLoopSourceRef source;
   
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(888);
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                    &yes, sizeof(yes));
    setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
                    &yes, sizeof(yes));
    bind(sock, (struct sockaddr *)&sin, sizeof(sin));
    listen(sock, 5);
   
    s = CFSocketCreateWithNative(NULL, sock,
                                                kCFSocketAcceptCallBack,
                                                acceptConnection,
                                                NULL);
   
    source = CFSocketCreateRunLoopSource(NULL, s, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source,
                                   kCFRunLoopDefaultMode);
    CFRelease(source);
    CFRelease(s);
    CFRunLoopRun();
}
// Client
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
void receiveData(CFSocketRef s,
                         CFSocketCallBackType type,
                         CFDataRef address,
                         const void *data,
                         void *info)
{
    CFDataRef df = (CFDataRef) data;
    int len = CFDataGetLength(df);
    if(len <= 0) return;
   
    CFRange range = CFRangeMake(0,len);
    UInt8 buffer[len];
    NSLog(@"Received %d bytes from socket %d\n",
                  len, CFSocketGetNative(s));
    CFDataGetBytes(df, range, buffer);
    NSLog(@"Client received: %s\n", buffer);
    NSLog(@"As UInt8 coding: %@", df);
}
 
int main ()
{
    CFSocketRef s = CFSocketCreate(NULL, PF_INET,
                                            SOCK_STREAM, IPPROTO_TCP,
                                            kCFSocketDataCallBack,
                                            receiveData,
                                            NULL);
    struct sockaddr_in      sin;
    struct hostent           *host;
   
    host = gethostbyname("localhost");     
    memset(&sin, 0, sizeof(sin));
    memcpy(&(sin.sin_addr), host->h_addr,host->h_length);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(888);
 
    CFDataRef address, data;
    UInt8 message[] = "Hello world";
    CFRunLoopSourceRef source;
 
    address = CFDataCreate(NULL, (UInt8 *)&sin, sizeof(sin));
    data = CFDataCreate(NULL, message, sizeof(message));
   
    CFSocketConnectToAddress(s, address, 0);
    CFSocketSendData(s, NULL, data, 0);
   
    CFRelease(address);
    CFRelease(data);
   
    source = CFSocketCreateRunLoopSource(NULL, s, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(),
                       source,
                       kCFRunLoopDefaultMode);
    CFRelease(source);
    CFRelease(s);
    CFRunLoopRun();
}
 
 
 
 
 
 
 
 
 

 

Non-blocking iPhone Echo Client

The above client is a simple but nearly useless example, it only executes a single send to the echo server; there are no external events to initiate further sends.

However, the code provides a basis for an event-driven iPhone application; one having separate connection and send events.

The server need not be altered.

Client

On Connect button event:

  1. Disables the Connect button to prevent additional connections.
  2. Creates a CFSocket with a callback to notify our method receiveData when data is received.
  3. Connects to the server via the socket.
  4. Creates a CFRunLoop to wait for receive data on the socket.
  5. Starts the CFRunLoop, the client is waiting for socket data.

On Send button event:

  1. Sends data out the connected socket.
  2. When data is received from the server, receiveData is called, the data is displayed in a UITextView object.

Points of note:

Non-blocking iPhone Echo Client using Berkley Socket Primitives and CFSocket
#import "EchoClientViewController.h"
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
 
UITextView *mTextViewAlias;
CFSocketRef s;

void receiveData(CFSocketRef s,
                 CFSocketCallBackType type,
                 CFDataRef address,
                 const void *data,
                 void *info)
{
    CFDataRef df = (CFDataRef) data;
    int len = CFDataGetLength(df);
    if(len <= 0) return;
   
    CFRange range = CFRangeMake(0,len);
    UInt8 buffer[len];
    NSLog(@"Received %d bytes from socket %d\n",
                len, CFSocketGetNative(s));
    CFDataGetBytes(df, range, buffer);
    NSLog(@"Client received: %s\n", buffer);
    NSLog(@"As UInt8 coding: %@", df);
    [mTextViewAlias setText:
                    [NSString stringWithFormat: @"%s", buffer]];
}
 
@implementation EchoClientViewController
 
-(IBAction) sendClicked:(id)sender{
    UInt8 message[] = "Hello world";
    CFDataRef data =
                 CFDataCreate(NULL, message, sizeof(message));
    CFSocketSendData(s, NULL, data, 0);
    CFRelease(data);
}
 
-(IBAction) connectClicked:(id)sender{
    UIButton *uiButton = (UIButton *) sender;
    [uiButton setEnabled: NO];
   
    mTextViewAlias = mTextView;
   
    s = CFSocketCreate(NULL, PF_INET,
                       SOCK_STREAM, IPPROTO_TCP,
                       kCFSocketDataCallBack,
                       receiveData,
                       NULL);
    struct sockaddr_in      sin;
    struct hostent          *host;
   
    memset(&sin, 0, sizeof(sin));
    host = gethostbyname("localhost");     
    memcpy(&(sin.sin_addr), host->h_addr,host->h_length);
 
    sin.sin_family = AF_INET;
    sin.sin_port = htons(1234);
 
    CFDataRef address;
    CFRunLoopSourceRef source;
 
    address = CFDataCreate(NULL, (UInt8 *)&sin, sizeof(sin));
    CFSocketConnectToAddress(s, address, 0);
   
    CFRelease(address);
   
    source = CFSocketCreateRunLoopSource(NULL, s, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(),
                                       source,
                                       kCFRunLoopDefaultMode);
    CFRelease(source);
    CFRunLoopRun();
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {}
- (void)dealloc {
    [super dealloc];
}
 
@end
#import <UIKit/UIKit.h>

@interface EchoClientViewController :
                               UIViewController
{
    IBOutlet UITextView *mTextView;
}

-(IBAction) connectClicked:(id)sender;
-(IBAction) sendClicked:(id)sender;
@end

 

 

NSStream

The above examples are useful for insight on the basics of custom TCP networking. Here we examine a class that is appropriate for client-side networking.

To keep the example simple, the UI consists of 3 buttons:

  1. Connect to a fixed echo server address and port.
  2. Send "Hello World" to the echo server.
  3. Disconnect from the echo server.

The iPhone app sends "Hello World", displays the echoed received. To see the output, examine the console for logging of execution output.

Several weaknesses to the above networking approaches:

Communication ports, which need to service events asynchronously, that is without blocking the current thread, can be added to a run loop. The port is then handled within the thread's run loop without blocking other services in the same thread.

NSStream is designed for connecting to and reading and writing data streams, whether from a file or network connection.

NSStream can be run in an NSRunLoop, allowing networking operations to occur without blocking UI execution.

The following schedules inputStream, which reads data from a port, on the currentRunLoop.

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                                      forMode:NSDefaultRunLoopMode];

For a working example using NSStream and NSRunLoop, download iPhone Echo Client using NSStream and NSRunLoop.

NSStream is straight-forward to use, with the only one complication on the iPhone.

NSHost, which resolves hostnames, has not been implemented on the iPhone, however a NSStream category will be created that uses Core Foundations to connect to the host and establish streams.

+(void) getStreamsToHostNamed: is an Additions category class method that implements host name resolution for NSStream.

Note that pass-by-reference is simulated in this method call, the diagram at right may serve as a reminder of pass-by-reference semantics.

Preferences

Chapter 9 of the iPhone SDK Development text describes preferences for user configuration of application settings.

The IP address can be changed in the Settings, the following reads the current Address preference or if no preference then sets to localhost.

   NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   NSString *address = [defaults stringForKey:@"Address"];
   if(!address) address = @"localhost";

Settings.bundle contains the definitions in Root.pfile for the application settings.

Opening as a Plain Text File reveals the underlying XML defintions:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
   <key>Title</key>
   <string>iPhoneClient</string>
   <key>StringsTable</key>
   <string>Root</string>
   <key>PreferenceSpecifiers</key>
   <array>
    <dict>
      <key>Type</key>
      <string>PSTextFieldSpecifier</string>
      <key>Title</key>
      <string>Server IP</string>
      <key>Key</key>
      <string>Address</string>
      <key>DefaultValue</key>
      <string>localhost</string>
      <key>IsSecure</key>
      <false/>
      <key>KeyboardType</key>
      <string>NumbersAndPunctuation</string>
      <key>AutoCorrectType</key>
      <string>No</string>
    </dict>
   </array>
  </dict>
</plist>

 

iPhoneClient
#import <UIKit/UIKit.h>

@interface iPhoneClientViewController : UIViewController {
	NSInputStream *iStream;
	NSOutputStream *oStream;
}
	
- (IBAction) connectCommand: (id)sender;
- (IBAction)sendText:(id)sender;	
- (IBAction) disconnectCommand: (id) sender;
@end

 

#import "iPhoneClientViewController.h"

@interface NSStream (Additions)
+ (void)getStreamsToHostNamed:(NSString *)hostName 
					   	    port:(NSInteger)port 
				  	  inputStream:(NSInputStream **)inputStreamPtr 
				 	outputStream:(NSOutputStream **)outputStreamPtr;
@end

@implementation NSStream (Additions)				
+ (void)getStreamsToHostNamed:(NSString *)hostName 
					   	    port:(NSInteger)port 
				  	  inputStream:(NSInputStream **)inputStreamPtr 
				 	outputStream:(NSOutputStream **)outputStreamPtr
{
    CFReadStreamRef     readStream;
    CFWriteStreamRef    writeStream;
	
    assert(hostName != nil);
    assert( (port > 0) && (port < 65536) );
    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );
	
    readStream = NULL;
    writeStream = NULL;
	
    CFStreamCreatePairWithSocketToHost(
				NULL, 
				(CFStringRef) hostName, 
				port, 
				((inputStreamPtr  != nil) ? &readStream : NULL),
				((outputStreamPtr != nil) ? &writeStream : NULL)
	);
	
    if (inputStreamPtr != NULL) {
        *inputStreamPtr  = [NSMakeCollectable(readStream) autorelease];
    }
    if (outputStreamPtr != NULL) {
        *outputStreamPtr = [NSMakeCollectable(writeStream) autorelease];
    }
}
@end

@implementation iPhoneClientViewController

-(void) connect {
   NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   NSString *address = [defaults stringForKey:@"Address"];
   if(!address) address = @"localhost";
    [NSStream getStreamsToHostNamed: address port:1490 
 				   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];
}

-(void) disconnect {
    [iStream close];
    [oStream close];
    [iStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [oStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [iStream setDelegate:nil];
    [oStream setDelegate:nil];
    [iStream release];
    [oStream release];
    iStream = nil;
    oStream = nil;
}

- (IBAction)connectCommand:(id)sender {
	if( iStream != nil) return;
	[self connect];
}

- (IBAction) disconnectCommand: (id) sender {
	if(iStream == nil) return;
	[self disconnect];
}

- (IBAction)sendText:(id)sender {
    NSString * stringToSend = [NSString stringWithFormat:@"%@\n", @"Hello world"];
    NSData * dataToSend = [stringToSend dataUsingEncoding:NSUTF8StringEncoding];
    if (oStream) {
        int remainingToWrite = [dataToSend length];
        void * marker = (void *)[dataToSend bytes];
        while (0 < remainingToWrite) {
            int actuallyWritten = 0;
            actuallyWritten = [oStream write:marker maxLength:remainingToWrite];
            remainingToWrite -= actuallyWritten;
            marker += actuallyWritten;
        }
    }
}

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
     NSString *io;
     if (theStream == iStream) io = @">>";
     else io = @"<<";
	
     NSString *event;
     switch (streamEvent)
     {
	case NSStreamEventHasBytesAvailable:
	      event = @"NSStreamEventHasBytesAvailable";
	      if (theStream == iStream) {		//read data
		uint8_t buffer[1024];
		int len;
		while ([iStream hasBytesAvailable]) {
		    len = [iStream read:buffer maxLength:sizeof(buffer)];
		    if (len > 0) {
			NSString *input = [[NSString alloc] initWithBytes:buffer 
									 length:len 
								        encoding:NSASCIIStringEncoding];
			if (nil != input) 		//do something with data
				NSLog(@"%@",input);
		     }
		}
	      }
	      break;
     	case NSStreamEventNone:
                  event = @"NSStreamEventNone - Can not connect to the host!";
  	       break;
	case NSStreamEventOpenCompleted:
	       event = @"NSStreamEventOpenCompleted";
	       break;
	case NSStreamEventHasSpaceAvailable:
	      event = @"NSStreamEventHasSpaceAvailable";
	      break;
	case NSStreamEventErrorOccurred:
	      event = @"NSStreamEventErrorOccurred";
	      break;
	case NSStreamEventEndEncountered:
	      event = @"NSStreamEventEndEncountered";
	      [self disconnect];
	      break;
	default:
 	      event = @"** Unknown";
       }	
       NSLog(@"%@ : %@", io, event);
}

-(void) viewDidLoad {
	iStream = nil;
	oStream=nil;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)dealloc {
    [super dealloc];
}
@end

 

 

 

Below is a Python echo server, listening on localhost port 1490.

echoServer.py
# echoserver.py

import socket

HOST = ''          # Symbolic name
PORT = 1490     # port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr

while True:
    data = conn.recv(1024)
    if not data: break
    print data
    conn.send(data)
conn.close()

 

AsyncSocket

AsyncSocket is one of several wrappers in the public domain that abstract out Internet working details.

The download section includes the source and an example echo client/server for Mac OS X.

Test:

Server:

Build: add the framework: /System/Library/Frameworks/CoreServices.framework

Open a terminal and navigate to AsyncSocket/Server/build/Debug and enter: AsyncSocket 2000

Client:

Build and Run: add the framework: /System/Library/Frameworks/CoreServices.framework

connect localhost 2000

Non-blocking OS X Echo Client using AsyncSocket
#import "AsyncSocket.h"
  
@interface Echo : NSObject  {
    AsyncSocket *socket;
}

-(void)runLoop;
-(void) connectHost: (NSString *) host port: (UInt16) port;
-(void)readFromServer;
-(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err;
-(void)onSocketDidDisconnect:(AsyncSocket *)sock;
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t;
@end
 

@implementation Echo : NSObject
 
- (id)init   {           // Create socket.
    self = [super init];
    socket = [[AsyncSocket alloc] initWithDelegate:self];
    return self;
}
 
- (void)dealloc {
    [socket release];
    [super dealloc];
}
 
- (void)runLoop  {               // Send “Hello world” 5 times, in 1 second intervals.
    int n;
    NSData *data = [@"Hello world\n" dataUsingEncoding:NSASCIIStringEncoding];
   
    for( n=0; n<5; n++)   {
        [socket writeData: data withTimeout:-1 tag:0];
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    }
}
 
-(void) connectHost: (NSString *) host port: (UInt16) port {
    @try    {
        NSError *err;
       
        if ([socket connectToHost:host onPort:port error: &err])
            NSLog (@"Connecting to %@ port %u.", host, port);
        else
            NSLog (@"Couldn't connect to %@ port %u (%@).", host, port, err);
    }
    @catch (NSException *exception) {
        NSLog ([exception reason]);
    }  
}
 
- (void)readFromServer {
    NSData *newline = [@"\n" dataUsingEncoding:NSASCIIStringEncoding];
    [socket readDataToData:newline withTimeout:-1 tag:0];
}
 
// Called whenever about to disconnect.
-(void) onSocket: (AsyncSocket *)sock willDisconnectWithError: (NSError *)err
{
    if (err != nil)
        NSLog (@"Socket will disconnect. Error domain %@, code %d (%@).",
               [err domain], [err code], [err localizedDescription]);
    else
        NSLog (@"Socket will disconnect. No error.");
}
 
// Place to release socket and perform the appropriate housekeeping and notification.
-(void) onSocketDidDisconnect:(AsyncSocket *)sock {
    NSLog (@"Disconnected.");
}
 
// Called when connected, immediately queue for incoming data.
-(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
    NSLog (@"Connected to %@ %u.", host, port);
    [self readFromServer];
}
 
/* Called when finished reading a packet. Immediately calls -readFromServer,
    to queue up read operation, waiting for the next packet.
*/
-(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)t
{
    NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
    NSLog(@"%@", str );
    [str release];
    [self readFromServer];
}
@end
 
int main (int argc, const char * argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Echo *e = [[Echo alloc] init];

    [e connectHost: @"localhost" port: 2000];
    [e runLoop];
    [e release];
    [pool release];
    return 0;
}

 

iPhone Client/Server

Three important ideas are presented:

  1. Implementing a class that calls delegate methods.
  2. Building an symmetric TCP application, that is it can operate as a server or client.
  3. Using an open source, OO class definition for high-level socket applications.

TCP communications between two (or more) iPhones can be implemented by:

Connecting as clients to a server that passes messages between the two (or more) iPhones. Because IP address of iPhones communicating over the data service of the cellular network dynamically change, communicating through a fixed IP server is often the more practical. Each iPhone is a client and, assuming the application is symmetric (e.g. chess game), uses a common application.

One is a server and the other a client. Appropriate for longer-lived IP addresses, such as WiFi. The advantage is that no central, third-party server is required. Ideally, a single version of the application acts as either the client or server, depending upon user or programmatic decisions. That is the approach taken in the following example.

Overview

The application will simply send a text message between two devices, one acting as a server, the other a client.

The overall design structure will be based on the AsyncSocket class.

The inheritance hierarchy at right illustrates commonality of function and data but also specialization.

TCP defines functions and data applicable to client or server as listed in the following:

@interface TCP : NSObject
{
    AsyncSocket *socket;
    id          theDelegate;
}
@property (readwrite, retain) id theDelegate;  
 
-(void) dealloc;
-(void) sendData: (NSString *) nsData;
-(void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err;
-(void) onSocket:(AsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag;
-(void) onSocketDidDisconnect: (AsyncSocket *)sock;
@end
 
@interface TCP (TCPDelegate)
-(void) receiveData: (NSString *) data;
-(void) didConnectToHost;
-(void) didDisconnectFromHost;
@end

Note that three delegate methods provide callback points to an application using TCP or a subclass. The delegate method name identifies the event.

TCPClient extends TCP by:

-initWithDelegate to create a client socket and set the delegate.

-connectHost:port to connect to a server.

@interface TCPClient : TCP {}
-(TCPClient *) initWithDelegate: (id) delegate;
-(BOOL) connectHost: (NSString *) host port: (UInt16) port;
@end

TCPServer extends TCP by adding a server socket property for accepting connections and:

-initWithDelegate: to create a server socket and set the delegate.

-didAcceptNewSocket: called when client connection accepted, passed a new socket for the client.

-didConnectToHost: called when connection to client is made and data can be read/written. Calls the delegate -didConnectToHost.

-startServer: starts the server on designated port. Server runs in a NSRunLoop in order to wait for data and connections without blocking the main thread.

@interface TCPServer : TCP
{
    AsyncSocket *serverSocket;
}
-(TCPServer *) initWithDelegate: (id) delegate;
-(void) onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *) newSocket;
-(void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
-(void) startServer: (NSString *) port;
-(void) dealloc;
@end

iPhone application can operate as a client or server based on user choice.

iPhone TCP Client/Server
#import <UIKit/UIKit.h>
#import "TCPClient.h"
#import "TCPServer.h"
 
@interface iPhoneClientServerViewController :
                                         UIViewController {
    IBOutlet UITextView *mTextView;
    TCP *tcp;
}
   
-(IBAction) asClientClicked:(id)sender;
-(IBAction) asServerClicked:(id)sender;
-(IBAction) sendClicked:(id)sender;  
@end

#import "iPhoneClientServerViewController.h"
 
@implementation iPhoneClientServerViewController
 
-(IBAction) asClientClicked:(id)sender{
    tcp = [[TCPClient alloc] initWithDelegate: self];
    [(TCPClient *) tcp connectHost: @"localhost" port: 2000];
    mTextView.text = @"Client Waiting";
}
 
-(IBAction) asServerClicked:(id)sender{
    tcp = [[TCPServer alloc] initWithDelegate: self];
    mTextView.text = @"Server Waiting";
    [ (TCPServer *) tcp startServer: @"2000"]; 
}
 
-(IBAction) sendClicked:(id)sender{
    if(tcp)
          [tcp sendData: @"Hello world\n"];
}
 
-(void) receiveData: (NSString *) data {
    mTextView.text = data;
}
 
-(void) didConnectToHost {
    mTextView.text = @"Connected";
}
 
-(void) didDisconnectFromHost {
    mTextView.text = @"Disconnected";
}
 
- (void)loadView {   [super loadView]; } 
- (void)viewDidUnload {} 
- (void)dealloc {  [super dealloc]; }
- (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];

@end

Some obvious but simple improvements:

  1. user input of server IP
  2. user input of data to send.

At least one hack:

Suppose the user operates as a server and then switches to client.

TCPServer thread should be stopped, resources are deallocated and the server port is closed.

Stopping the server properly requires stopping the thread.

The server, as to be non-blocking, is executed on a NSRunLoop as follows:

shouldKeepRunning = YES;

NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

Stopping the server thread requires externally setting:

shouldKeepRunning = NO;

 

Delegates

The above application uses delegation for passing event notification from TCP networking layer to the application.

Below illustrates the delegate method -didDisconnectFromHost.

There are five steps to delegate use:

  1. Define an id object to reference the delegate object and delegate methods for delegates to implement.
  2. The delegate implements delegate methods and registers itself with the notifying class as a delegate.
  3. The delegate object is set in the notifying class.
  4. The notifying class calls any implemented delegate methods.
Delegation
1.   Definition of TCP class.

@interface TCP : NSObject
{
    id          theDelegate;
}
@property (readwrite, retain) id theDelegate;  
 
@interface TCP (TCPDelegate)
-(void) receiveData: (NSString *) data;
-(void) didConnectToHost;
-(void) didDisconnectFromHost;
@end

3. -initWithDelegate: method in TCPClient class.

- (TCPClient *) initWithDelegate: (id) delegate{
    self = [super init];   
    [self setTheDelegate: delegate];
    NSLog (@"Creating socket.");
    socket = [[AsyncSocket alloc] initWithDelegate:self];
    return self;
}

2. iPhoneClientServerViewController registers
    itself as a delegate.

#import "TCP.h"

@implementation iPhoneClientServerViewController
 
-(IBAction) asClientClicked:(id)sender{
    tcp = [[TCPClient alloc] initWithDelegate: self];
    [(TCPClient *) tcp connectHost: @"localhost" port: 2000];
    mTextView.text = @"Client Waiting";
}

-(void) didDisconnectFromHost {
   mTextView.text = @"Disconnected";
}

4. TCPClient method that calls delegate method on disconnect event.

-(void) onSocketDidDisconnect: (AsyncSocket *) sock {
    NSLog (@"onSocketDidDisconnect.");
    if ([theDelegate respondsToSelector:@selector(didDisconnectFromHost)]) 
        [theDelegate didDisconnectFromHost];
}

 

Bonjour - Service Discovery

Client/server applications seen so far expect the server to be known to the client beforehand. For example, a mapping app would be hardwired to connect the client to maps.google.com server.

Service discovery allows an application to discover hosts advertizing available services.

Bonjour is a service discovery protocol for local networking (WiFi, Bluetooth, Ethernet) using multicast Domain Name Service records.

The following is our typical echo client/server example with the following difference:

  1. Echo server running on OS X, advertises cocoaecho service under tcp protocol.
  2. Echo client on iPhone, discovers hosts advertizing cocoaecho service under tcp protocol.

NSNetServiceBrowser provides service discovery where minimally:

Client

 Server

Our example is slightly more complicated but realistic. The client:

Client

- (void)awakeFromNib {
    serviceBrowser = [[NSNetServiceBrowser alloc] init];
    serviceList = [[NSMutableArray alloc] init];
    [serviceBrowser setDelegate:self];
   
    [serviceBrowser
         searchForServicesOfType:@"_cocoaecho._tcp."
         inDomain:@""];
}
 
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser
           didFindService:(NSNetService *)aNetService
           moreComing:(BOOL)moreComing {
    if (![serviceList containsObject:aNetService]) {
        [self willChangeValueForKey:@"serviceList"];
        [serviceList addObject:aNetService];
        [self didChangeValueForKey:@"serviceList"];
    }
    else [serviceList addObject:aNetService];
    [serverTableView reloadData];                  // Copy serviceList to table
}
Server
 

NSNetService *netService;

netService  = [[NSNetService alloc]
                     initWithDomain: @""
                     type: @"_cocoaecho._tcp."
                     name: nil
                     port: 2000];

[netService publish];

 

UITableView

The echo client provides a table of service provider choices.

In the figure below, the iPhone detects two providers of the cocoaecho service under tcp protocol.

The user clicks on one to select and connect. Clicking Send sends "Hello" to server which echoes and is displayed.

Table display and selection of the service provider requires:

  1. Start the service discovery (discussed above).
  2. When each provider is discovered, add the provider's NSNetService record into an array.
  3. Load the table from the array.

The table and array entries then correspond, the table displaying the name of the service provider from the NSNetService record referenced in the array.

Interface Builder requires three connections for the UITableView object outlets:

  1. dataSource of Table View connect to File Owner,
  2. delegate of Table View connect to File Owner,
  3. serverTableView IBOutlet connect to Table View.

Four UITableView methods are implemented:

  1. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView returns the number of sections in table, here there is 1.
     
  2. -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section returns the number of rows in section, the number of providers discovered.
     
  3. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath returns a cell for table at indexpath. Called when the table data is loaded. Here service providers are stored in an array and displayed in the table in the same order. The method simply maps the array entry to the corresponding table entry.
     
  4. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath called when user clicks a table entry, passing the table row, used to access corresponding array entry, a NSNewService object of the selected service provider. That object is used to open streams to the service providing server.
#import <UIKit/UIKit.h>
 
@interface iPhoneEchoClientViewController : UIViewController {
    IBOutlet UITextField      * inputField;
    IBOutlet UITextField      * responseField;
    IBOutlet UILabel           * serverLabel;
    IBOutlet UITableView * serverTableView;
    NSNetServiceBrowser   * serviceBrowser;
    NSMutableArray           * serviceList;
    NSInputStream            * inputStream;
    NSOutputStream          * outputStream;
    NSMutableData            * dataBuffer;
}
- (IBAction)sendText:(id)sender;
- (void)openStreams;
- (void)closeStreams;
@end
#import "iPhoneEchoClientViewController.h"
 
@implementation iPhoneEchoClientViewController
 
- (IBAction)sendText:(id)sender {
    NSString * stringToSend = [NSString stringWithFormat:@"%@\n", [inputField text]];
    NSData * dataToSend = [stringToSend dataUsingEncoding:NSUTF8StringEncoding];
    if (outputStream) {
        int remainingToWrite = [dataToSend length];
        void * marker = (void *)[dataToSend bytes];
        while (0 < remainingToWrite) {
            int actuallyWritten = 0;
            actuallyWritten = [outputStream write:marker maxLength:remainingToWrite];
            remainingToWrite -= actuallyWritten;
            marker += actuallyWritten;
        }
    }
}
 
- (void)awakeFromNib {
    serviceBrowser = [[NSNetServiceBrowser alloc] init];
    serviceList = [[NSMutableArray alloc] init];
    [serviceBrowser setDelegate:self];
   
    [serviceBrowser searchForServicesOfType:@"_cocoaecho._tcp." inDomain:@""];
}
#pragma mark -
#pragma mark UITableView delegate methods
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  return 1;  }
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [serviceList count];
}

// Asks the data source for a cell to insert in a particular location (indexPath) of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath {
    static NSString *CellIdentifier = @"Cell";
   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    }

    [cell.textLabel setText: [[serviceList objectAtIndex:indexPath.row] name]];                // Cell contents
    return cell;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (inputStream && outputStream) {
        [self closeStreams];
    }
   
    if (indexPath.row) {
        NSNetService * selectedService = [serviceList objectAtIndex:indexPath.row];
        if ([selectedService getInputStream: &inputStream outputStream: &outputStream]) {
            [self openStreams];
        }
        serverLabel.text = [[serviceList objectAtIndex:indexPath.row] name];
    }
}

#pragma mark -
#pragma mark NSNetServiceBrowser delegate methods
 
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser
            didFindService:(NSNetService *)aNetService
            moreComing:(BOOL)moreComing {
    if (![serviceList containsObject:aNetService]) {
        [self willChangeValueForKey:@"serviceList"];
        [serviceList addObject:aNetService];
        [self didChangeValueForKey:@"serviceList"];
    }
    else [serviceList addObject:aNetService];
    [serverTableView reloadData];
}
 
- (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser
            didRemoveService:(NSNetService *)aNetService
            moreComing:(BOOL)moreComing {
    if ([serviceList containsObject:aNetService]) {
        [self willChangeValueForKey:@"serviceList"];
        [serviceList removeObject:aNetService];
        [self didChangeValueForKey:@"serviceList"];
    }
}
#pragma mark -
#pragma mark Stream methods
 
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent {
    NSInputStream * istream;
    switch(streamEvent) {
        case NSStreamEventHasBytesAvailable:
            uint8_t oneByte;
            int actuallyRead = 0;
            istream = (NSInputStream *)aStream;
            if (!dataBuffer) {
                dataBuffer = [[NSMutableData alloc] initWithCapacity:2048];
            }
            actuallyRead = [istream read:&oneByte maxLength:1];
            if (actuallyRead == 1) {
                [dataBuffer appendBytes:&oneByte length:1];
            }
            if (oneByte == '\n') {
                // We've got the carriage return at the end of the echo. Let's set the string.
                NSString * string = [[NSString alloc] initWithData:dataBuffer encoding:NSUTF8StringEncoding];
                [responseField setText:string];
                [string release];
                [dataBuffer release];
                dataBuffer = nil;
            }
            break;
        case NSStreamEventEndEncountered:
            [self closeStreams];
            break;
        case NSStreamEventHasSpaceAvailable:
        case NSStreamEventErrorOccurred:
        case NSStreamEventOpenCompleted:
        case NSStreamEventNone:
        default:
            break;
    }
}
 
- (void)openStreams {
    [inputStream retain];
    [outputStream retain];
    [inputStream setDelegate:self];
    [outputStream setDelegate:self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];
}
 
- (void)closeStreams {
    [inputStream close];
    [outputStream close];
    [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream setDelegate:nil];
    [outputStream setDelegate:nil];
    [inputStream release];
    [outputStream release];
    inputStream = nil;
    outputStream = nil;
}
 
- (void)viewDidLoad {
    [super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {}
- (void)dealloc {
    [super dealloc];
}
@end

NSStream

NSStream is used to stream data between the client and server.

The client initiates the connection to the selected service provider by:

NSNetService * selectedService = [serviceList objectAtIndex:indexPath.row];
if ([selectedService getInputStream: &inputStream outputStream: &outputStream])
            [self openStreams];

-openStreams method opens and starts threads to handle stream input/output.

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent the method called on stream events, the client application is a NSStream delegate. Two events are handled:

  1. NSStreamEventHasBytesAvailable when input.
  2. NSStreamEventEndEncountered when connection closed.