客户端我们使用iPhone应用程序,画面比较简单。点击发送按钮,给服务器发送一些字符串过去。点击接收按钮就会从服务器读取一些字符串,并且显示在画面上。
有关客户端应用的UI部分不再介绍了,我们直接看代码部分,Socket客户端可以采用CFStream或NSStream实现,CFStream 实现方式与服务器端基本一样。为了给读者介绍更多的知识,本例我们采用NSStream实现。NSStream实现采用Objective-C语言,一些 面向对象的类。
下面我们看看客户端视图控制器ViewController.h
#import <CoreFoundation/CoreFoundation.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 9000 @interface ViewController : UIViewController<NSStreamDelegate> { int flag ; //操作标志 0为发送 1为接收 } @property (nonatomic, retain) NSInputStream *inputStream; @property (nonatomic, retain) NSOutputStream *outputStream; @property (weak, nonatomic) IBOutlet UILabel *message; - (IBAction)sendData:(id)sender; - (IBAction)receiveData:(id)sender; @end
定义属性inputStream和outputStream,它们输入流NSInputStream和输出流NSOutputStream类。它们与服务器CFStream实现中的输入流CFReadStreamRef和输出流CFWriteStreamRef对应的。
视图控制器ViewController.m的初始化网络方法initNetworkCommunication代码:
- (void)initNetworkCommunication { CFReadStreamRef readStream; CFWriteStreamRef writeStream; CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@”192.168.1.103″, PORT, &readStream, &writeStream); ① _inputStream = (__bridge_transfer NSInputStream *)readStream; ② _outputStream = (__bridge_transfer NSOutputStream*)writeStream; ③ [_inputStream setDelegate:self]; ④ [_outputStream setDelegate:self]; ⑤ [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; ⑥ [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; ⑦ [_inputStream open]; ⑧ [_outputStream open]; ⑨ }
点击发送和接收按钮触发的方法如下:
/* 点击发送按钮 */ - (IBAction)sendData:(id)sender { flag = 0; [self initNetworkCommunication]; } /* 点击接收按钮 */ - (IBAction)receiveData:(id)sender { flag = 1; [self initNetworkCommunication]; }
它们都调用initNetworkCommunication方法,并设置操作标识flag,如果flag为0发送数据,flag为1接收数据。
流的状态的变化触发很多事件,并回调NSStreamDelegate协议中定义的方法stream:handleEvent:,其代码如下:
-(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { NSString *event; switch (streamEvent) { case NSStreamEventNone: event = @”NSStreamEventNone”; break; case NSStreamEventOpenCompleted: event = @”NSStreamEventOpenCompleted”; break; case NSStreamEventHasBytesAvailable: event = @”NSStreamEventHasBytesAvailable”; if (flag ==1 && theStream == _inputStream) { NSMutableData *input = [[NSMutableData alloc] init]; uint8_t buffer[1024]; ① int len; while([_inputStream hasBytesAvailable]) ② { len = [_inputStream read:buffer maxLength:sizeof(buffer)]; ③ if (len > 0) { [input appendBytes:buffer length:len]; } } NSString *resultstring = [[NSString alloc] initWithData:input encoding:NSUTF8StringEncoding]; NSLog(@”接收:%@”,resultstring); _message.text = resultstring; } break; case NSStreamEventHasSpaceAvailable: event = @”NSStreamEventHasSpaceAvailable”; if (flag ==0 && theStream == _outputStream) { //输出 UInt8 buff[] = ”Hello Server!”; ④ [_outputStream write:buff maxLength: strlen((const char*)buff)+1]; ⑤ //关闭输出流 [_outputStream close]; } break; case NSStreamEventErrorOccurred: event = @”NSStreamEventErrorOccurred”; [self close]; ⑥ break; case NSStreamEventEndEncountered: event = @”NSStreamEventEndEncountered”; NSLog(@”Error:%d:%@”,[[theStream streamError] code], [[theStream streamError] localizedDescription]); break; default: [self close]; ⑦ event = @”Unknown”; break; } NSLog(@”event??%@”,event); }
在读取数据分支(NSStreamEventHasBytesAvailable)中,代码第①行为读取数据准备缓冲区,本例中设置的是1024个字节,这个大小会对流的读取有很多的影响。第②行代码使用hasBytesAvailable方法判断是否流有数据可以读,如果有可读数据就进行循环读取。第③行代码使用流的read:maxLength:方法读取数据到缓冲区,第1个参数是缓冲区对象buffer,第2个参数是读取的缓冲区的字节长度。
在写入数据分支(NSStreamEventHasSpaceAvailable)中,代码第④行是要写入的数据,第⑤行代码 [_outputStream write:buff maxLength: strlen((const char*)buff)+1]是写如数据方 法。
第⑥和第⑦行代码[self close]调用close方法关闭,close方法代码如下:
-(void)close { [_outputStream close]; [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream setDelegate:nil]; [_inputStream close]; [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream setDelegate:nil]; }