zoukankan      html  css  js  c++  java
  • 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] = ''
    31             printf("server said:%s ",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] = '';
    58         printf("client said:%s ",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 ",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 ",buf);
    44             } else {
    45                 NSLog(@"no buffer!");
    46             }
    47             break;
    48         }     
    49     }
    50 }

    4 参考文献

          [1] Stream Programming Guide.

          [2] Networking Programming Topics

     

  • 相关阅读:
    CF1435E Solo mid Oracle(推柿子)
    CF1435C Perform Easily(Set)
    NC7501I (反向建图)
    NC7501D Router Mesh (去掉割点算连通快的变化)
    超全的Python第三方库
    字符串常见题目
    IDEA的常用设置大全
    IDEA的介绍与安装
    涨姿势啦(1)
    Torch常用的函数总结
  • 原文地址:https://www.cnblogs.com/huliangwen/p/5470079.html
Copyright © 2011-2022 走看看