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
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
- Creates socket.
- Binds socket to port 8888.
- Blocks, waiting for connection at accept.
- When connection, blocks waiting for data to be received.
- When data received, reads and sends using socket.
- Closes connection.
Client
- Creates socket.
- Connects to localhost on port 8888.
- When connected, sends data.
- Blocks, waiting for echoed data to be received.
- Closes connection.
// Server import socket |
// Client import socket |
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 sin_family member is always AF_INET,
- the sin_port member is a 16-bit port number,
- and the sin_addr member is a 32-bit IP address.
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.
- 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.
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
- Creates and binds a server socket on port 888.
- Blocks execution while listening for a connection.
- When connected, blocks while waiting to receive data.
- Sends data received.
- Closes connection
Client
- Creates a client socket to port 888 on localhost.
- Blocks execution while waiting for a connection.
- When connected, sends data "Hello world".
- Blocks while waiting to receive echoed data.
- Prints data received.
- Closes connection
// 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:
- 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.
- Use CFSocket which creates a thread and notifies the application.
Server
- Constructs a BSD server socket as before through calling the listen function.
- 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.
- Starts the CFRunLoop, the server is waiting for a connection.
- 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.
- Starts the CFRunLoop, the server is waiting for data.
- When data is received from a client, receiveData is called, the data is displayed and echoed back to the client.
Client
- Creates a CFSocket with a callback to notify our method receiveData when data is received.
- Connects to the server via the socket.
- Sends data out the socket.
- Creates a CFRunLoop to wait for receive data on the socket.
- Starts the CFRunLoop, the client is waiting for socket data.
- When data is received from the server, receiveData is called, the data is displayed.
Points of note:
- The server creates a new socket and starts a new CFRunLoop for each connection, it can handle multiple clients.
- The function call CFRunLoopRun() does not return. Not a problem in event driven applications but any following statements are not reached.
- This is pure C code, no Objective C to be found.
// 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:
- Disables the Connect button to prevent additional connections.
- Creates a CFSocket with a callback to notify our method receiveData when data is received.
- Connects to the server via the socket.
- Creates a CFRunLoop to wait for receive data on the socket.
- Starts the CFRunLoop, the client is waiting for socket data.
On Send button event:
- Sends data out the connected socket.
- When data is received from the server, receiveData is called, the data is displayed in a UITextView object.
Points of note:
- CFSocket is C not Objective C.
- C can call Objective C methods and Objective C can call C functions.
- Problem that C cannot access objects within the Objective C scope.
mTextView in Objective C scope, not visible in C scope.
- Solution defines id mTextViewAlias visible in C and Objective C scopes; within Objective C scope (i.e. method) alias by:
mTextViewAlias = mTextView;
which can then be used in C function receiveData.
|
#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 |

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:
- Connect to a fixed echo server address and port.
- Send "Hello World" to the echo server.
- 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:
- Not pure Objective C.
- Not event driven networking.
- No delegates.
- No error checking.
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>
|
#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)
@end
@implementation iPhoneClientViewController
-(void) connect {
[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
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
|
#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 |
iPhone Client/Server
Three important ideas are presented:
- Implementing a class that calls delegate methods.
- Building an symmetric TCP application, that is it can operate as a server or client.
- 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 o
r 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.
|
#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:
- user input of server IP
- 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:
- Define an id object to reference the delegate object and delegate methods for delegates to implement.
- The delegate implements delegate methods and registers itself with the notifying class as a delegate.
- The delegate object is set in the notifying class.
- The notifying class calls any implemented delegate methods.
|
1. Definition of TCP class. @interface TCP : NSObject |
3. -initWithDelegate: method in TCPClient class. - (TCPClient
*) initWithDelegate: (id) delegate{ |
|
2. iPhoneClientServerViewController registers itself as a delegate. #import "TCP.h" -(void) didDisconnectFromHost { |
4. TCPClient method that calls delegate method on disconnect event. -(void) onSocketDidDisconnect: (AsyncSocket *) sock { |
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:
- Echo server running on OS X, advertises cocoaecho service under tcp protocol.
- Echo client on iPhone, discovers hosts advertizing cocoaecho service under tcp protocol.
NSNetServiceBrowser provides service discovery where minimally:
Client
- Sets self as delegate and implements didFindService delegate method.
- Starts service discovery.
- When didFindService called, connects to a host advertizing the service in the usual way.
Server
- Publishes advertizing service, only type and port are necessary.
- Enters wait for connection.
Our example is slightly more complicated but realistic. The client:
- Constructs an array of available service providers ad providers are reported to the didFindService delegate method.
- The array is loaded into a UITableView for the user to view.
- User can select desired provider from the table.
| 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]
|
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:
- Start the service discovery (discussed above).
- When each provider is discovered, add the provider's NSNetService record into an array.
- 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:
- dataSource of Table View connect to File Owner,
- delegate of Table View connect to File Owner,
- serverTableView IBOutlet connect to Table View.
Four UITableView methods are implemented:
- -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView returns the number of sections in table, here there is 1.
- -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section returns the number of rows in section, the number of providers discovered.
- -(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.
- -(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. |
|
#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:
- NSStreamEventHasBytesAvailable when input.
- NSStreamEventEndEncountered when connection closed.