iOS 网络编程:socket

1 Socket基础

      在IOS中,根据不同的语言环境可以使用不同的方法来创建socket连接。

1) 在Objective-C语言环境使用NSStream类API

     *如果知道远程主机的 DNS或者是IP 地址,那么可以使用 CFStreamCreatePairWithSocketToHost 或者函数 CFStreamCreatePairWithSocketToCFHost创建 core foundation的连接,然后将CFStream对象toll-free bridged转换为NSStream 对象。
     *也可以传递给 CFStreamCreatePairWithSocketToNetService函数 一个CFNetServiceRef对象,来创建一个到Bonjour 服务器上的连接。

2) 在C语言环境使用CFStream类API

      可以使用低级别的CFStream API来创建socket连接,这种方式与NSStream API的使用方式一样,也是通过三个函数来创建与远程主机的socket连接:CFStreamCreatePairWithSocketToHostCFStreamCreatePairWithSocketToCFHostCFStreamCreatePairWithSocketToNetService。只是不需要将其转换为NSStream 对象,其使用方式与第1种类似。  

3) 在跨平台环境使用POSIX调用

      也可以使用POSIX类型的socket连接,但是如果在OS X 和iOS系统中,应避免使用这种方式,因为其使用方式非常繁琐。特别是不要在GUI主线程中使用同步方式的POSIX连接,因为这样会影响用户体验。

2 BSD Socket

2.1 简介

      UNIX内核加入TCP/IP协议的时候,便在系统中引入了一种新的IO操作,只不过由于网络连接的不可靠性,所以网络IO比本地设备的IO复杂很多。这一系列的接口叫做BSD Socket API,当初由伯克利大学研发,最终成为网络开发接口的标准。 网络通信从本质上讲也是进程间通信,只是这两个进程一般在网络中不同计算机上。

      由于本文重点是讨论IOS的socket编程,并且Apple官网也不推荐使用BSD socket编程,所以这里只稍微纪录,若需详细研究可以参考《UNIX网络编程卷1:套接字联网API(第3版)》和另一篇笔记《Socket知识整理》。

2.2 基本程序

      socket连接由TCP和UDP两种类型,而TCP的使用频率较高,下面参考《UNIX网络编程卷1:套接字联网API(第3版)》的基本TCP连接图,实现一个简单的例子,其中这个例子是UNIX程序,即在MAC系统中也可正确执行。

图 21 基本TCP客户端/服务器socket连接图

2.2.1 Client端程序

 1 #include <stdio.h> 
 2 #include <netinet/ in.h> 
 3 #include <sys/socket.h> 
 4 #include <arpa/inet.h> 
 5 #include < string.h> 
 6  
 7  int main ( int argc,  const  char * argv[]) 
 8 { 
 9      struct sockaddr_in server_addr; 
10     server_addr.sin_len =  sizeof( struct sockaddr_in); 
11     server_addr.sin_family = AF_INET; 
12     server_addr.sin_port = htons( 11332); 
13     server_addr.sin_addr.s_addr = inet_addr( " 127.0.0.1 "); 
14     bzero(&(server_addr.sin_zero), 8); 
15      
16      int server_socket = socket(AF_INET, SOCK_STREAM,  0); 
17      if (server_socket == - 1) { 
18         perror( " socket error "); 
19          return  1
20     } 
21      char recv_msg[ 1024]; 
22      char reply_msg[ 1024]; 
23      
24      if (connect(server_socket, ( struct sockaddr *)&server_addr,  sizeof( struct sockaddr_in))== 0)  { 
25          // connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。 
26           while ( 1) { 
27             bzero(recv_msg,  1024); 
28             bzero(reply_msg,  1024); 
29              long byte_num = recv(server_socket,recv_msg, 1024, 0); 
30             recv_msg[byte_num] =  ' \0 '
31             printf( " server said:%s\n ",recv_msg); 
32              
33             printf( " reply: "); 
34             scanf( " %s ",reply_msg); 
35              if (send(server_socket, reply_msg,  10240) == - 1) { 
36                 perror( " send error "); 
37             } 
38         }    
39     }      
40      return  0
41 }

 

2.2.2 Server端程序

 1 #include <stdio.h>
 2 #include <netinet/ in.h>
 3 #include <sys/socket.h>
 4 #include <arpa/inet.h>
 5 #include < string.h>
 6 
 7  int main ( int argc,  const  char * argv[])
 8 {
 9      struct sockaddr_in server_addr;
10     server_addr.sin_len =  sizeof( struct sockaddr_in);
11     server_addr.sin_family = AF_INET; // Address families AF_INET互联网地址簇
12      server_addr.sin_port = htons( 11332);
13     server_addr.sin_addr.s_addr = inet_addr( " 127.0.0.1 ");
14     bzero(&(server_addr.sin_zero), 8);
15 
16      // 创建socket
17       int server_socket = socket(AF_INET, SOCK_STREAM,  0); // SOCK_STREAM 有连接
18       if (server_socket == - 1) {
19         perror( " socket error ");
20          return  1;
21     }
22 
23      // 绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信
24       int bind_result = bind(server_socket, ( struct sockaddr *)&server_addr,  sizeof(server_addr));
25      if (bind_result == - 1) {
26         perror( " bind error ");
27          return  1;
28     }
29 
30      // listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满>了,且有新的连接的时候,对方可能会收到出错信息。
31       if (listen(server_socket,  5) == - 1) {
32         perror( " listen error ");
33          return  1;
34     }
35 
36      struct sockaddr_in client_address;
37     socklen_t address_len;
38      int client_socket = accept(server_socket, ( struct sockaddr *)&client_address, &address_len);
39      // 返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。
40       if (client_socket == - 1) {
41         perror( " accept error ");
42          return - 1;
43     }
44 
45      char recv_msg[ 1024];
46      char reply_msg[ 1024];
47 
48      while ( 1) {
49         bzero(recv_msg,  1024);
50         bzero(reply_msg,  1024);
51 
52         printf( " reply: ");
53         scanf( " %s ",reply_msg);
54         send(client_socket, reply_msg,  10240);
55 
56          long byte_num = recv(client_socket,recv_msg, 1024, 0);
57         recv_msg[byte_num] =  ' \0 ';
58         printf( " client said:%s\n ",recv_msg);
59 
60     }
61 
62      return  0;
63 }

 

3 NSStream Socket

3.1 Cocoa Streams

3.1.1 NSStream相关类

         Cocoa Streams包含三个相关的类: NSStream、NSInputStream 和NSOutputStream。

  • NSStream:是个抽象类,定义了一些基本属性和方法;
  • NSInputStream:是NSStream的子类,可通过它从NSData、File和socket中读取数据流
  • NSOutputStream:也是NSStream的子类,可通过它将数据流写入NSData、File和socket。

 

图 31 NSInputStream和NSOutputStream数据转换图

3.1.2 NSStreamDelegate

      还可以给stream对象设置Delegate(NSStreamDelegate),如果没有精确了给stream指定Delegate,那么默认将Delegate设置为其自己。

NSStreamDelegate只有一个方法:

- ( void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;

         NSStreamEvent有多种类型,主要的是NSStreamEventHasBytesAvailable,表示已经可以从输入stream对象中读取数据了,或是写入的数据已经被接收了。

3.1.3 与CFStream比较

     NSStream是基于CFStream创建的,所以可以将NSInputStream 和NSOutputStream转换为CFWriteStream 和CFReadStream。虽然NSStream和CFStream非常相似,但是它们仍有所不同,NSStream是Cocoa API,它是通过设置delegate类实现异步行为CFStream是Core Foundation API,它是通过设置回调函数来实现异步的行为

3.2 通过NSInputStream 读数据

      在Cocoa中,通过NSInputStream对象读数据,可以分为如下步骤完成:

       a) 从数据源创建初始化一个NSInputStream对象;

       b) 配置run loop打开stream对象;

       c) 响应NSInputStream事件NSStreamDelegate

       d) 关闭NSInputStream对象。

如下例子是打开一个Document目录下的文件"theFile.txt",该文件预先创建好的。

 1 - ( void)viewDidLoad {
 2 [super viewDidLoad];
 3     NSArray *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,  true);
 4     NSString *myDocPath = [document objectAtIndex: 0];
 5     NSString* fileName = [myDocPath stringByAppendingPathComponent: @" theFile.txt "];
 6     
 7     [self setUpStreamForFile:fileName];
 8 }
 9 
10 - ( void)setUpStreamForFile:(NSString *)path {  // 自定义方法,初始化input Stream,并启动读文件
11      NSInputStream *iStream = [[NSInputStream alloc] initWithFileAtPath:path];
12     [iStream setDelegate:self];
13     [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
14     [iStream open];
15 }
16 - ( void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {  // 实现协议方法,响应事件。
17      NSMutableData *_data;
18      switch(eventCode) {
19          case NSStreamEventHasBytesAvailable:
20         {
21             uint8_t buf[ 1024];
22             unsigned  int len =  0;
23 len = [(NSInputStream *)stream read:buf maxLength: 1024];     // 当有可读数据时,才开始读。
24  printf( " %s\n ",buf);
25               break;
26         }
27          case NSStreamEventEndEncountered:
28         {
29             [stream close];
30             [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
31             stream = nil; 
32              break;
33         }
34     }
35 }

3.3 通过NSOutputStream写数据

      在Cocoa中,通过NSOutputStream对象写数据,可以分为如下步骤完成:

       a) 从数据源创建初始化一个NSOutputStream对象;

       b) 配置run loop打开stream对象

       c) 响应NSOutputStream事件NSStreamDelegate

       d) 关闭NSOutputStream对象。

 

如下例子是打开一个Document目录下的文件"theFile.txt",并将数据写入该文件中。

 1 - ( void)viewDidLoad {
 2     [super viewDidLoad];
 3     NSArray *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,  true);
 4     NSString *myDocPath = [document objectAtIndex: 0];
 5     NSString* fileName = [myDocPath stringByAppendingPathComponent: @" theFile.txt "];
 6     
 7     [self createOutputStream:fileName]; 
 8 }
 9 
10 - ( void)createOutputStream:(NSString *)path
11 {
12     NSOutputStream* oStream = [[NSOutputStream alloc] initToFileAtPath:path append: true];
13     [oStream setDelegate:self];
14     [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
15     [oStream open];
16 }
17 
18 - ( void)setUpStreamForFile:(NSString *)path {
19     NSInputStream *iStream = [[NSInputStream alloc] initWithFileAtPath:path];
20     [iStream setDelegate:self];
21     [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
22     [iStream open];
23 }
24 - ( void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
25 {
26     NSOutputStream *oStream = stream;
27      switch(eventCode) {
28          case NSStreamEventHasSpaceAvailable:
29         {
30             uint8_t buf[]= " hello my lover ";
31             unsigned  int len = strlen(buf)+ 1;
32             [oStream write:( const uint8_t *)buf maxLength:len];
33             [oStream close];
34              break;
35         }
36          case NSStreamEventEndEncountered:
37         {
38             [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
39             oStream = nil; 
40              break;
41         }
42     }
43 }

 

3.4 建立socket stream

3.4.1 实现原理

      由于NSStream类不支持在IOS平台上创建socket连接,而CFStream支持在IOS平台的socket行为。所以若知道远程主机的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函数来创建socket连接,通过该函数创建了CFStream类型为全双工的socket连接,接着可以利用toll-free bridge,将CFStream对象转换为NSStream对象

      CFStreamCreatePairWithSocketToHost函数是基于TCP协议创建的socket连接,其函数原型是:

void CFStreamCreatePairWithSocketToHost(CFAllocatorRef alloc, CFStringRef host, UInt32 port, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);

 

      通过NSStream对象进行Socket通信,与通过NSStream进行IO操作的步骤基本一样:

       a) 创建NSStream对象,通过CFStreamCreatePairWithSocketToHost函数创建CFReadStreamRef 和CFWriteStreamRef 对象;继而将两者转换为NSInputStreamNSOutputStream 对象;

       b) 配置run loop打开NSInputStream NSOutputStream对象

       c) 响应事件,在Delegate中响应不同的信号;

       d) 关闭NSStream对象。

3.4.2 示例

      如下是由NSStream实现的socket client,其中socket server可以使用2章节的例子配合测试。实现的功能是进行client和server消息的收发。

1 - ( void)viewDidLoad {  // 该方法是IOS的入口方法。
2      [super viewDidLoad];
3     NSString *urlStr = [NSString stringWithFormat: @" 127.0.0.1 "];
4     [self searchForSite:urlStr]; 
5 }

 

 1 - (IBAction)searchForSite:(NSString *)urlStr  // 该方法实现的功能是创建socket连接,并启动对socket描述符进行监听。
 2  {
 3         CFReadStreamRef readStream;
 4         CFWriteStreamRef writeStream;
 5          // 该方法就是通过CFStream创建的socket连接
 6          CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)urlStr,  11332, &readStream, &writeStream);
 7         
 8         NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;      // 实现转换
 9          NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream; // 实现转换
10         
11         [inputStream setDelegate:self];   // 设置代理
12          [outputStream setDelegate:self];
13 
14         [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
15         [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
16 
17         [inputStream open];
18         [outputStream open];
19         
20          /*  Store a reference to the input and output streams so that they don't go away....  */
21     }
22 }

 

 1 - ( void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode  // 该方法是NSStreamDelegate协议方法,对事件的响应方法
 2 
 3      switch(eventCode) {
 4          case NSStreamEventHasSpaceAvailable:    // 可写的事件响应处理
 5          {
 6             NSOutputStream *oStream = stream; // 因NSStream不能调用write方法,故需强制转换为NSOutputStream。
 7              uint8_t buf[]= " hello socket ";
 8             unsigned  int len = strlen(buf)+ 1;
 9             [oStream write:( const uint8_t *)buf maxLength:len];
10              break;
11         }
12          case NSStreamEventEndEncountered:   // 结束事件
13          {
14             [stream close];
15             [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
16             stream = nil;  //  stream is ivar, so reinit it
17               break
18         }
19          case NSStreamEventNone:
20         {
21              break;
22         }
23          case NSStreamEventOpenCompleted:   // 打开完成事件
24          {
25             NSLog( @" NSStreamEventOpenCompleted ");
26              break;
27         }
28          case NSStreamEventErrorOccurred:   // 错误发生事件
29          {
30             NSError *theError = [stream streamError];
31             NSLog( @" Error %i: %@ ", [theError code], [theError localizedDescription]);
32             [stream close];
33              break;
34         }
35          case NSStreamEventHasBytesAvailable:   // 可读的事件响应处理
36          {
37             NSMutableData *_data;
38             uint8_t buf[ 1024];
39             unsigned  int len =  0;
40             len = [(NSInputStream *)stream read:buf maxLength: 1024]; 
41              if(len) {
42                 [_data appendBytes:( const  void *)buf length:len];
43                 printf( " %s\n ",buf);
44             }  else {
45                 NSLog( @" no buffer! ");
46             }
47              break;
48         }     
49     }
50 }

 

4 参考文献

      [1] Stream Programming Guide.

      [2] Networking Programming Topics

 

转载于:https://www.cnblogs.com/huliangwen/p/5470079.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值