本例源于关东升同名书籍,有兴趣可以去买一本,我手头就一本,初看,还不错:
所有网络编程步骤都大同小异:
服务器的工作流程:
1. 服务器调用 socket(...) 创建socket; 2. 服务器调用 listen(...) 设置缓冲区; 3. 服务器通过 accept(...)接受客户端请求建立连接; 4. 服务器与客户端建立连接之后,就可以通过 send(...)/receive(...)向客户端发送或从客户端接收数据; 5. 服务器调用 close 关闭 socket;
客户端工作流程:
1. 客户端调用 socket(...) 创建socket; 2. 客户端调用 connect(...) 向服务器发起连接请求以建立连接; 3. 客户端与服务器建立连接之后,就可以通过 send(...)/receive(...)向客户端发送或从客户端接收数据; 4. 客户端调用 close 关闭 socket;
由于 iOS 设备通常是作为客户端,下文将演示如何编写客户端代码。
客户端我们使用iPhone应用程序,画面比较简单。点击发送按钮,给服务器发送一些字符串过去。点击接收按钮就会从服务器读取一些字符串,并且显示在画面上。
有关客户端应用的UI部分不再介绍了,我们直接看代码部分,Socket客户端可以采用CFStream或NSStream实现,CFStream 实现方式与服务器端基本一样。为了给读者介绍更多的知识,本例我们采用NSStream实现。NSStream实现采用Objective-C语言,一些 面向对象的类。
下面我们看看客户端视图控制器ViewController.h
1 #import <CoreFoundation/CoreFoundation.h> 2 3 #include <sys/socket.h> 4 5 #include <netinet/in.h> 6 7 8 9 #define PORT 9000 10 11 12 13 @interface ViewController : UIViewController<NSStreamDelegate> 14 15 { 16 17 int flag ; //操作标志 0为发送 1为接收 18 19 } 20 21 22 23 @property (nonatomic, retain) NSInputStream *inputStream; 24 25 @property (nonatomic, retain) NSOutputStream *outputStream; 26 27 28 29 @property (weak, nonatomic) IBOutlet UILabel *message; 30 31 32 33 - (IBAction)sendData:(id)sender; 34 35 - (IBAction)receiveData:(id)sender; 36 37 38 39 @end
定义属性inputStream和outputStream,它们输入流NSInputStream和输出流NSOutputStream类。它们与服务器CFStream实现中的输入流CFReadStreamRef和输出流CFWriteStreamRef对应的。
视图控制器ViewController.m的初始化网络方法initNetworkCommunication代码:
1 - (void)initNetworkCommunication 2 3 { 4 5 CFReadStreamRef readStream; 6 7 CFWriteStreamRef writeStream; 8 9 CFStreamCreatePairWithSocketToHost(NULL, 10 11 (CFStringRef)@”192.168.1.103″, PORT, &readStream, &writeStream); ① 12 13 _inputStream = (__bridge_transfer NSInputStream *)readStream; ② 14 15 _outputStream = (__bridge_transfer NSOutputStream*)writeStream; ③ 16 17 [_inputStream setDelegate:self]; ④ 18 19 [_outputStream setDelegate:self]; ⑤ 20 21 [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 22 23 forMode:NSDefaultRunLoopMode]; ⑥ 24 25 [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 26 27 forMode:NSDefaultRunLoopMode]; ⑦ 28 29 [_inputStream open]; ⑧ 30 31 [_outputStream open]; ⑨ 32 33 }
点击发送和接收按钮触发的方法如下:
1 /* 点击发送按钮 */ 2 3 - (IBAction)sendData:(id)sender { 4 5 flag = 0; 6 7 [self initNetworkCommunication]; 8 9 } 10 11 /* 点击接收按钮 */ 12 13 - (IBAction)receiveData:(id)sender { 14 15 flag = 1; 16 17 [self initNetworkCommunication]; 18 19 }
它们都调用initNetworkCommunication方法,并设置操作标识flag,如果flag为0发送数据,flag为1接收数据。
流的状态的变化触发很多事件,并回调NSStreamDelegate协议中定义的方法stream:handleEvent:,其代码如下:
1 -(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { 2 3 NSString *event; 4 5 switch (streamEvent) { 6 7 case NSStreamEventNone: 8 9 event = @”NSStreamEventNone”; 10 11 break; 12 13 case NSStreamEventOpenCompleted: 14 15 event = @”NSStreamEventOpenCompleted”; 16 17 break; 18 19 case NSStreamEventHasBytesAvailable: 20 21 event = @”NSStreamEventHasBytesAvailable”; 22 23 if (flag ==1 && theStream == _inputStream) { 24 25 NSMutableData *input = [[NSMutableData alloc] init]; 26 27 uint8_t buffer[1024]; ① 28 29 int len; 30 31 while([_inputStream hasBytesAvailable]) ② 32 33 { 34 35 len = [_inputStream read:buffer maxLength:sizeof(buffer)]; ③ 36 37 if (len > 0) 38 39 { 40 41 [input appendBytes:buffer length:len]; 42 43 } 44 45 } 46 47 NSString *resultstring = [[NSString alloc] 48 49 initWithData:input encoding:NSUTF8StringEncoding]; 50 51 NSLog(@”接收:%@”,resultstring); 52 53 _message.text = resultstring; 54 55 } 56 57 break; 58 59 case NSStreamEventHasSpaceAvailable: 60 61 event = @”NSStreamEventHasSpaceAvailable”; 62 63 if (flag ==0 && theStream == _outputStream) { 64 65 //输出 66 67 UInt8 buff[] = ”Hello Server!”; ④ 68 69 [_outputStream write:buff maxLength: strlen((const char*)buff)+1]; ⑤ 70 71 //关闭输出流 72 73 [_outputStream close]; 74 75 } 76 77 break; 78 79 case NSStreamEventErrorOccurred: 80 81 event = @”NSStreamEventErrorOccurred”; 82 83 [self close]; ⑥ 84 85 break; 86 87 case NSStreamEventEndEncountered: 88 89 event = @”NSStreamEventEndEncountered”; 90 91 NSLog(@”Error:%d:%@”,[[theStream streamError] code], 92 93 [[theStream streamError] localizedDescription]); 94 95 break; 96 97 default: 98 99 [self close]; ⑦ 100 101 event = @”Unknown”; 102 103 break; 104 105 } 106 107 NSLog(@”event——%@”,event); 108 109 }
在读取数据分支(NSStreamEventHasBytesAvailable)中,代码第①行为读取数据准备缓冲区,本例中设置的是1024个字节,这个大小会对流的读取有很多的影响。第②行代码使用hasBytesAvailable方法判断是否流有数据可以读,如果有可读数据就进行循环读取。第③行代码使用流的read:maxLength:方法读取数据到缓冲区,第1个参数是缓冲区对象buffer,第2个参数是读取的缓冲区的字节长度。
在写入数据分支(NSStreamEventHasSpaceAvailable)中,代码第④行是要写入的数据,第⑤行代码 [_outputStream write:buff maxLength: strlen((const char*)buff)+1]是写如数据方 法。
第⑥和第⑦行代码[self close]调用close方法关闭,close方法代码如下:
1 -(void)close 2 3 { 4 5 [_outputStream close]; 6 7 [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] 8 9 forMode:NSDefaultRunLoopMode]; 10 11 [_outputStream setDelegate:nil]; 12 13 [_inputStream close]; 14 15 [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] 16 17 forMode:NSDefaultRunLoopMode]; 18 19 [_inputStream setDelegate:nil]; 20 21 }