开发文章

iOS系统socket网络编程

相关概念

  • socket是一个针对TCP和UDP编程的接口,你可以借助它建立TCP连接等。socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口。

  • socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。

  • TCP连接的三次握手:
    第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

    握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”。

  • socket建立网络连接的步骤
    建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
    套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
    1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
    2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
    3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

BSD socket

BSD socket:完全由C语言实现,并且可以在Objective-C代码中使用。
优点:不同平台中易于移植
缺点:
无法访问操作系统内建的网络特性(比如系统范围的VPN)。
更糟糕的是初始化socket连接并不会自动打开设备的Wi-Fi或是蜂窝网络,无线网络会智能的关闭以节省电池电量,任何通信连接都会失败,除非其他网络进程激活了无线网。

CFNetwork对BSD Socket的分装可以激活设备的无线网,所以几乎所有场景都建议使用CFNetwork而不是BSD Socket.

最常用的API:
1.int socket(int, int, int):创建并初始化新的socket,如果成功放回一个文件描述符,如果失败返回0。
2.int bind(int, const struct sockaddr *, socklen_t):为指定的地址与端口号分配socket。
3.int listen(int, int) __DARWIN_ALIAS(listen):用于服务端监听客户端
4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受连接请求,将客户端地址存储到clientAddress中。
5.int connect(int, const struct sockaddr *, socklen_t):连接到指定的服务器。
6.ssize_t send(int, const void *, size_t, int):在socket上最多发送XX数据。
7.ssize_t recv(int, void *, size_t, int):在socket上最多读取XX数据。
……

服务端逻辑:如果接收到客户端发送过来”1”,那么回复”你猜!”;客户端发送过来”2”,那么回复”谢谢!”;客户端发送过来”3”,那么回复”对不起!”;客户端发送过来”4”,那么回复”好的!”;客户端发送过来其他的内容,那么一半概率原样回复,一半概率回复”不知道你在说什么!”。

服务端逻辑.png

创建服务端:
1.创建socket

复制内容到剪贴板
  1. struct sockaddr_in server_addr;  
  2.  server_addr.sin_len = sizeof(struct sockaddr_in);  
  3.  server_addr.sin_family = AF_INET;//AF_INET互联网地址簇  
  4.  server_addr.sin_port = htons(port);  
  5.  server_addr.sin_addr.s_addr = inet_addr(address);  
  6.  bzero(&server_addr.sin_zero, 8);  
  7.   
  8.  //创建socket  
  9.  int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有链接  
  10.   
  11.  if (server_socket == -1) {  
  12.      perror("socket error!");  
  13.   
  14.      return 1;  
  15.  }  

2.绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信

复制内容到剪贴板
  1. int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));  
  2.  if (bind_result == -1) {  
  3.      perror("bind error!");  
  4.   
  5.      return 1;  
  6.  }  

3.listen侦听

复制内容到剪贴板
  1. if (listen(server_socket, 5)) {  
  2.      perror("listen error!");  
  3.   
  4.      return 1;  
  5.  }  

4.接受客户端连接

复制内容到剪贴板
  1. int client_socket;  
  2.   socklen_t address_len;  
  3.   struct sockaddr_in client_address;  
  4.   for (; ; ) {  
  5.       address_len = sizeof(client_address);  
  6.       client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len);  
  7.       //1.使用多线程  
  8.       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  9.           //这个函数进行数据操作  
  10.           str_echo(client_socket);  
  11.       });  
  12.   }  

5.接收和发送数据

复制内容到剪贴板
  1. char buf[1024];  
  2.   
  3.   while (1) {  
  4.       bzero(buf, 1024);  
  5.       long byte_num = recv(socket, buf, 1024, 0);  
  6.       if (byte_num < 0) {  
  7.           return;  
  8.       }  
  9.       buf[byte_num] = '\0';  
  10.       printf("client said:%s\n", buf);  
  11.   
  12.       char *result;  
  13.       NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];  
  14.       if ([str isEqualToString:@"1"]) {  
  15.           result = "你猜!";  
  16.       }  
  17.       else if ([str isEqualToString:@"2"])  
  18.       {  
  19.           result = "谢谢!";  
  20.       }  
  21.       else if ([str isEqualToString:@"3"])  
  22.       {  
  23.           result = "对不起!";  
  24.       }  
  25.       else if ([str isEqualToString:@"4"])  
  26.       {  
  27.           result = "好的!";  
  28.       }  
  29.       else  
  30.       {  
  31.           if (arc4random()%2 == 0) {  
  32.               result = "不知道你在说什么!";  
  33.           }  
  34.           else  
  35.           {  
  36.               result = buf;  
  37.           }  
  38.       }  
  39.   
  40.       send(socket, result, 1024, 0);  
  41.   }  

创建客户端:

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

NSStream 读写文件

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

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

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

NSStreamDelegate只有一个方法
  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode

NSStreamEventNone :改默认值不代表任何事件
NSStreamEventOpenCompleted :socket被成功打开
NSStreamEventHasBytesAvailable :有可以读取的字节
NSStreamEventHasSpaceAvailable :缓存中可以写入字节
NSStreamEventErrorOccurred :操作出现错误CFReadStreamCopyError()和CFWriteStreamCopyError()会提供更多错误细节
NSStreamEventEndEncountered :socket达到字节流的末尾

通过NSStream对象读数据,可以分为如下步骤完成:
a) 从数据源创建和初始化一个NSStream对象;
b) 配置run loop,并打开stream对象;
c) 响应NSInputStream事件(NSStreamDelegate);
d) 关闭NSInputStream对象。

复制内容到剪贴板
  1.  (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.   
  4.     NSString *documentStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];  
  5.     NSString *dataPath = [documentStr stringByAppendingPathComponent:@"data.txt"];  
  6.   
  7.     NSFileManager *manager = [NSFileManager defaultManager];  
  8.     if ([manager fileExistsAtPath:dataPath]) {  
  9.         self.read = YES;  
  10.         NSLog(@"string:%@", [NSString stringWithContentsOfFile:dataPath encoding:NSUTF8StringEncoding error:nil]);  
  11.   
  12.         NSInputStream *inStream = [[NSInputStream alloc] initWithFileAtPath:dataPath];  
  13.         [inStream setDelegate:self];  
  14.         [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  15.         [inStream open];  
  16.     }  
  17.     else  
  18.     {  
  19.         if ([manager createFileAtPath:dataPath contents:nil attributes:nil]) {  
  20.             self.read = NO;  
  21.             NSOutputStream *outStream = [[NSOutputStream alloc] initToFileAtPath:dataPath append:YES];  
  22.             [outStream setDelegate:self];  
  23.             [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  24.             [outStream open];  
  25.         }  
  26.         else  
  27.         {  
  28.             NSLog(@"文件创建失败!");  
  29.         }  
  30.     }  
  31. }  
  32.   
  33. #pragma mark - <NSStreamDelegate>代理方法  
  34. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode  
  35. {  
  36.     if (self.read) {  
  37.         NSInputStream *inStream = (NSInputStream *)aStream;  
  38.         switch (eventCode) {  
  39.             case NSStreamEventHasBytesAvailable:  
  40.             {  
  41.                 uint8_t data[1024];  
  42.                 [inStream read:data maxLength:1024];  
  43.                 printf("%s", data);  
  44.                 [inStream close];  
  45.                 [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  46.                 inStream = nil;  
  47.             }  
  48.                 break;  
  49.             case NSStreamEventEndEncountered:  
  50.             {  
  51.                 [inStream close];  
  52.                 [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  53.                 inStream = nil;  
  54.             }  
  55.                 break;  
  56.   
  57.             default:  
  58.                 break;  
  59.         }  
  60.     }  
  61.     else{  
  62.         NSOutputStream *outStream = (NSOutputStream *)aStream;  
  63.         switch (eventCode) {  
  64.             case NSStreamEventHasSpaceAvailable:  
  65.             {  
  66.                 uint8_t data[] = "{name:'张三', age:10}";  
  67.                 [outStream write:data maxLength:strlen((char *)data)];  
  68.                 [outStream close];  
  69.                 [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  70.                 outStream = nil;  
  71.             }  
  72.                 break;  
  73.             case NSStreamEventEndEncountered:  
  74.             {  
  75.                 [outStream close];  
  76.                 [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  77.                 outStream = nil;  
  78.             }  
  79.                 break;  
  80.   
  81.             default:  
  82.                 break;  
  83.         }  
  84.     }  
  85. }  

NSStream Socket

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

通过NSStream对象进行Socket通信,与通过NSStream进行IO操作的步骤基本一样:
a) 创建NSStream对象,通过CFStreamCreatePairWithSocketToHost函数创建CFReadStreamRef 和CFWriteStreamRef 对象;继而将两者转换为NSInputStream 和NSOutputStream 对象;
b) 配置run loop,并打开NSInputStream 和NSOutputStream对象;
c) 响应事件,在Delegate中响应不同的信号;
d) 关闭NSStream对象。

服务端:服务端依然使用上面例子的服务端
客户端的实现:点击1~5这五个按钮,分别从服务端返回不同的数据,进而展示出来。

客户端的实现.png

由于实现相对简单,就不加阐述说明了,实现代码如下:

复制内容到剪贴板
  1. - (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.   
  4.     [self startSocket:@"127.0.0.1" andPort:11332];  
  5. }  
  6.   
  7. - (void)startSocket:(NSString *)address andPort:(int)port  
  8. {  
  9.     CFReadStreamRef readRef;  
  10.     CFWriteStreamRef writeRef;  
  11.   
  12.     CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)address, port, &readRef, &writeRef);  
  13.   
  14.     NSInputStream *inputStream = (__bridge NSInputStream *)readRef;  
  15.     NSOutputStream *outputStream = (__bridge NSOutputStream *)writeRef;  
  16.   
  17.     inputStream.delegate = self;  
  18.     outputStream.delegate = self;  
  19.   
  20.     [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  21.     [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  22.   
  23.     [inputStream open];  
  24.     [outputStream open];  
  25.   
  26.     self.outputStream = outputStream;  
  27. }  
  28.   
  29. - (IBAction)sendMsg:(UIButton *)sender {  
  30.     if (sender.currentTitle > 0) {  
  31.         const char *output = sender.currentTitle.UTF8String;  
  32.         [self.outputStream write:(const uint8_t *)output maxLength:strlen(output)];  
  33.     }  
  34. }  
  35.   
  36. - (void)showMessage:(NSString *)msg  
  37. {  
  38.     if (!self.msgLabel) {  
  39.         UIFont *font = [UIFont systemFontOfSize:14];  
  40.         UIColor *color = [UIColor blackColor];  
  41.         UIColor *backColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2];  
  42.   
  43.         UILabel *label = [[UILabel alloc] init];  
  44.         label.textColor = color;  
  45.         label.font = font;  
  46.         label.textAlignment = NSTextAlignmentCenter;  
  47.         label.backgroundColor = backColor;  
  48.         self.msgLabel = label;  
  49.     }  
  50.   
  51.     CGFloat screenW = [UIScreen mainScreen].bounds.size.width;  
  52.     CGFloat screenH = [UIScreen mainScreen].bounds.size.height;  
  53.     CGFloat W = screenW - 16;  
  54.     CGSize size = [msg boundingRectWithSize:CGSizeMake(W, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.msgLabel.font} context:nil].size;  
  55.     W = size.width;  
  56.     CGFloat H = size.height;  
  57.     CGFloat X = (screenW - W)/2;  
  58.     CGFloat Y = (screenH - H)/2;  
  59.   
  60.     self.msgLabel.frame = CGRectMake(X, Y, W, H);  
  61.     self.msgLabel.text = msg;  
  62.     [self.view addSubview:self.msgLabel];  
  63.   
  64.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{  
  65.         [self.msgLabel removeFromSuperview];  
  66.     });  
  67. }  
  68.   
  69. #pragma mark - <NSStreamDelegate>代理方法  
  70. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode  
  71. {  
  72.     switch (eventCode) {  
  73.         case NSStreamEventNone:  
  74.             break;  
  75.         case NSStreamEventOpenCompleted:  
  76.             break;  
  77.         case NSStreamEventHasBytesAvailable:  
  78.         {  
  79.             uint8_t buf[1024];  
  80.             NSInteger len = 0;  
  81.             NSInputStream *inputStream = (NSInputStream *)aStream;  
  82.             len = [inputStream read:buf maxLength:1024];  
  83.             if (len) {  
  84.                 [self showMessage:[NSString stringWithCString:(const char *)buf encoding:NSUTF8StringEncoding]];  
  85.             }  
  86.             break;  
  87.         }  
  88.         case NSStreamEventHasSpaceAvailable:  
  89.             break;  
  90.         case NSStreamEventErrorOccurred:  
  91.         {  
  92.             [aStream close];  
  93.             break;  
  94.         }  
  95.         case NSStreamEventEndEncountered:  
  96.         {  
  97.             [aStream close];  
  98.             [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];  
  99.             aStream = nil;  
  100.             break;  
  101.         }  
  102.   
  103.         default:  
  104.             break;  
  105.     }  
  106. }  

 

代码下载

服务端代码下载地址 https://github.com/jiuchabaikaishui/ServerSocket

客户端代码下载地址 https://github.com/jiuchabaikaishui/ClientSocket

 

感谢 酒茶白开水 支持 磐实编程网 原文地址:
blog.csdn.net/jiuchabaikaishui/article/details/62425730

文章信息

发布时间:2017-03-18

作者:酒茶白开水

发布者:aquwcw

浏览次数: