@Xiaojun-Jin
2014-09-04T01:58:54.000000Z
字数 5236
阅读 8300
iOS
Socket
Note from author: This is a learning note of Networking Tutorial for iOS: How To Create A Socket Based iPhone App and Server written by Ray Wenderlich.
A socket is a tool that allows you to transfer data in a bidirectional way. So a socket has two sides, each side is identified by a combination of two elements: the IP address and port.
There are many different types of sockets, that differ in the way data is transferred (protocol). The most popular types are TCP and UDP. In this tutorial, we'll deal with TCP sockets.
Before we write our iOS project, we're going to create TCP server using Python language. However, since the specific server implementation is not drectly related to iOS techology, we're here just skip it and assume that the server is ready. Now, let's focus on the App Client, and do some socket programming.
Solutions for possible error while connecting to port 80:
http://stackoverflow.com/questions/14640711/python-twisted-reactor-address-already-in-use
The client has to manage three main operations: Joining a chat room, Sending messages and Receiving messages.
To establish a socket connection on iOS we use streams. A stream is an abstraction over the mechanism of sending and receiving data. Moreover a stream has a delegate associated, which allows to react according to specific events like "the connection is open", "data have been received", "the connection has been closed", and so on.
There are there important classes related to streams included in the Cocoa Framework:
NSStream
This is the super class which defines some abstract features like open, close and delegate.NSInputStream
A subclass of NSStream for reading input.NSOutputStream
A subclass of NSSTream for writing output.
The only problem here is that NSStream class cannot connect to a remote host, while CFStream has the exactly ability. Fortunately, NSStream and CFStream are sort of bridged, so it's easy to get a NSStream form a CFStream.
NSInputStream *inputStream; NSOutputStream *outputStream;
- (void)initNetworkCommunication
{
CFReadStreamRef readStream; CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"localhost",
80, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self]; [outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open]; [outputStream open];
}
CFStreamCreatePairWithSocketToHost
helps us to bind two streams to a host and a port. Once you have called it you can freely cast CFStreams
to NSStreams
.
Our streams have to continuously be ready to send or receive data. To enable this we have to schedule the stream to receive events in a run loop. The app has to react to stream events but not be at the mercy of them. Run-loop scheduling allows to execute other code (if needed) but ensuring that you get notifications when something happens on the stream.
After connected to the server, we're ready to join the chat. Remember that the join message has the form "iam:name". So we need to build a string like that and write it to the outputStream.
- (IBAction)joinChat:(id)sender
{
NSString *res = [NSString stringWithFormat:@"iam:%@", inputNameField.text];
NSData *dt = [[NSData alloc] initWithData:[res dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[dt length]];
}
To dispatch events, the server needs to define a protocol defining an expected format/sequence of data that the client/server send back and forth. For this app, we're going to use a very simple string based protocol:
iam
means "user has joined the chat" andmsg
stands for "sending message".
We'll implement this in a way very similar to join chat – we just need to switch "iam:" with "msg:".
- (IBAction)sendMessage:(id)sender
{
NSString *res = [NSString stringWithFormat:@"msg:%@", inputMessageField.text];
NSData *dt = [[NSData alloc] initWithData:[res dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[dt length]];
}
To tell you the truth the app is also receiving messages from the server, and we need to implement the NSStream delegate stream:handleEvent:
which will enable our application to react to the activity happening on streams.
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
NSLog(@"stream event %i", streamEvent);
switch (streamEvent)
{
case NSStreamEventOpenCompleted:
NSLog(@"Stream opened");
break;
case NSStreamEventHasBytesAvailable: // fundamental to receive messages
if (theStream == inputStream)
{
uint8_t buffer[1024]; int len;
while ([inputStream hasBytesAvailable])
{
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output = [[NSString alloc] initWithBytes:buffer
length:len
encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(@"server said: %@", output);
}
}
}
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[theStream release];
theStream = nil;
break;
default:
NSLog(@"Unknown event");
}
}
We use a while loop to collect the bytes of the stream. The read method returns 0 when there is nothing left in the stream. So when the result is greater than zero we convert the buffer into a String and we print the result.
Running the app on the device:
First, remember to switch the "localhost" string with the ip of your computer. To find the current IP go to "System Preferences > Network". Your device should be connected via wireless to the same router which serves you computer. If you want to use 3G connection you should configure your router to accept connections from outside your network on the port 80 (not recommended).