zoukankan      html  css  js  c++  java
  • Socket&GCDAsyncSocket(异步Socket)

    Socket

    *********************************************

    简单理解Socket 就是网络连接,可以实现两个点之间的数据通讯。

    Socket允许使用长连接,允许应用程序运行在异步模式(提高效率),只有在需要的时候才接收数据
    使用Socket,可以只传送数据本身而不用进行XML封装,大大降低数据传输的开销      在(JSON)之前出现的

    iOS中常用的两种Socket类型

    Ø流式Socket(SOCK_STREAM):流式是一种面向连接的Socket,针对于面向连接的TCP服务应用
    Ø数据报式Socket(SOCK_DGRAM):数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用
     
    •在iOS中以NSStream(流)来发送和接收数据
    开发步骤
     
    {
    网络连接设置
      1.设置网络连接,绑定到主机和端口
      2.设置输入流和输出流的代理,监听数据流的状态
      3.将输入输出流添加至运行循环
      4.打开输入流和输出流
    发送消息给服务器
    有可读取字节时,读取服务器返回的内容
    到达流末尾时,关闭流,同时并从主运行循环中删除
    }
     

     通过Scoket可以实现所有的网络功能:包括:GET、POST、PUT、DELETE

    文件读取、写入(I/O)方式是以(二进制)流的方式读取的
     最主要的应用场景是:自定义的协议,编写自由的网络应用!
     
     Socket 的难点
     1. 因为所有的(I/O)输入输出都是在一个代理方法中调用,随着自定义协议的复杂度的提高,
        程序编写难度势必要大幅度提升。
     2. 多线程的处理!
       输入流和输出流都添加到了主运行循环,如果应用过于复杂,将影响主线程程序的性能
        因此,需要使用另外一个运行循环,专门管理输入输出流。
       代理方法的工作是对数据的输入输出流进行“解析”,解析工作同样不需要影响到主线程的工作。
     
     3.多线程方面的处理,是Socket的一大难点!可以使用第三方框架GCDAsyncSocket来解决多线程问题。

    连接服务器

    #pragma mark 连接到服务器
    - (void)connectToServer:(NSString *)hostName port:(NSInteger)port
    {
        // 设置网络
        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
      
        // CF框架是C语言的框架
        // 此方法可以连接到服务器,并分配输入流和输出流的内存空间
        CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostName, port, &readStream, &writeStream);
        
        // 记录已经分配的输入流和输出流
        _inputStream = (__bridge NSInputStream *)readStream;
        _outputStream = (__bridge NSOutputStream *)writeStream;
        
        // 设置代理,监听输入流和输出流中的变化
        _inputStream.delegate = self;
        _outputStream.delegate = self;
        
        // Scoket是建立的长连接,需要将输入输出流添加到主运行循环
        // 如果不将流加入主运行循环,delegate拒绝工作
        [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        
        // 打开输入流和输出流,准备开始文件读写操作
        [_inputStream open];
        [_outputStream open];
    }
    

     登录到聊天室

     1 #pragma mark 登录到聊天室
     2 - (IBAction)login
     3 {
     4     NSString *hostName = _hostName.text;
     5     NSInteger port = [_portText.text integerValue];
     6     
     7     [self connectToServer:hostName port:port];
     8     
     9     // 发送登录消息
    10     NSString *msg = [NSString stringWithFormat:@"iam:%@",      _nickNameText.text];
    11     // 在网络上发送的是二进制数据
    12     NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
    13     
    14     // 发送数据,直接往输入流写数据
    15     [_outputStream write:data.bytes maxLength:data.length];
    16 }

     NSStream的代理方法

    /**
     NSStreamEventNone = 0,                         // 无事件
     NSStreamEventOpenCompleted = 1UL << 0,         // 建立连接完成
     NSStreamEventHasBytesAvailable = 1UL << 1,     // 有可读的字节,接收到了数据,可以读了
     NSStreamEventHasSpaceAvailable = 1UL << 2,     // 可以使用输出流的空间,此时可以发送数据给服务器
     NSStreamEventErrorOccurred = 1UL << 3,         // 发生错误
     NSStreamEventEndEncountered = 1UL << 4         // 流结束事件,在此事件中负责做销毁工作
     */
    #pragma mark NSStream的代理方法
    
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
    {
        NSLog(@"%d", eventCode);
        
        switch (eventCode) {
            case NSStreamEventOpenCompleted:
                NSLog(@"连接完成");
                break;
            case NSStreamEventHasBytesAvailable:
                NSLog(@"有可读字节");
                // 读从服务器接收到得数据,从输入流中读取
                // 先开辟一段缓冲区以读取数据,用空间来换取程序的简单
                uint8_t buffer[1024];
                
                // read返回的是输入流缓冲区中实际存储的字节数
                NSInteger len = [_inputStream read:buffer maxLength:sizeof(buffer)];
                
                if (len > 0) { // 读到数据
                    // 将buffer中的数据,转换成字符串,输出
                    NSString *str = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
                    
                    // 将接收到的内容添加到数组
                    [_dataList addObject:str];
                    
                    // 刷新表格
                    [_tableView reloadData];
                }
                
                break;
            case NSStreamEventHasSpaceAvailable:
                NSLog(@"可以写入数据");
                break;
            case NSStreamEventErrorOccurred:
                NSLog(@"发生错误");
                break;
            case NSStreamEventEndEncountered:
                NSLog(@"流结束");
                // 做善后工作
                // 关闭流的同时,将流从主运行循环中删除
                [aStream close];
                [aStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            default:
                break;
        }
    }
    

     GCDAsyncSocket(异步Socket)

    需要导入Security.framework & CFNetwork.framework框架

    // 1. Socket通讯的第一件事情——先创建一个长连接
        _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    创建一个长连接
     1 #pragma mark - Socket代理方法
     2 #pragma mark 连接到主机
     3 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
     4 {
     5     NSLog(@"连接建立 %@ %@", host, [NSThread currentThread]);
     6     
     7     // 长连接建立完成后,给服务器发送 iam:昵称 通知服务器用户登录
     8     NSString *str = [NSString stringWithFormat:@"iam:%@", _nickNameText.text];
     9     // 网络通讯中,所有的数据都是以二进制流的模式传输的
    10     NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    11     
    12     // 将数据写入到输出流,告诉服务器用户登录
    13     [_socket writeData:data withTimeout:-1 tag:100];
    14 }
    15 
    16 #pragma mark 读数据
    17 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
    18 {
    19     NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    20     
    21     if (tag == 100) {
    22         NSLog(@"登录消息 %@", str);
    23     } else {
    24         NSLog(@"聊天消息 %@->%ld", str, tag);
    25     }
    26     
    27     NSLog(@"%@", [NSThread currentThread]);
    28     
    29     // 剩下的工作 绑定表格数据
    30     [_dataList addObject:str];
    31     
    32     // 在主线程刷新表格
    33     dispatch_async(dispatch_get_main_queue(), ^{
    34         [_tableView reloadData];
    35     });
    36 }
    37 
    38 #pragma mark 写数据
    39 #pragma mark Socket已经把数据写给了服务器
    40 - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
    41 {
    42     // 通过Log,我们发现在给服务器写入数据时,如果指定了tag,根据tag就知道发送(写给)的是哪一类的数据
    43     // 发现了读数据的代理方法没有被触发
    44     NSLog(@"写数据 %ld", tag);
    45     
    46     // 尝试让socket读一下数据,读取服务器返回的内容
    47     [_socket readDataWithTimeout:-1 tag:tag];
    48 }
    Socket代理方法
     1 #pragma mark - Action
     2 - (IBAction)connect:(id)sender
     3 {
     4     NSString *hostName = _hostName.text;
     5     int port = [[_portText text] intValue];
     6     
     7     // 连接到主机
     8     NSError *error = nil;
     9     if (![_socket connectToHost:hostName onPort:port error:&error]) {
    10         NSLog(@"%@", error.localizedDescription);
    11     } else {
    12         NSLog(@"OK");
    13     }
    14 }
    Action
  • 相关阅读:
    还在使用golang 的map 做Json编码么?
    Golang 性能测试(2) 性能分析
    golang 性能测试 (1) 基准性能测试
    消息队列 NSQ 源码学习笔记 (五)
    消息队列 NSQ 源码学习笔记 (四)
    消息队列 NSQ 源码学习笔记 (三)
    消息队列 NSQ 源码学习笔记 (二)
    消息队列 NSQ 源码学习笔记 (一)
    你不知道的空格
    Supervisor 使用和进阶4 (Event 的使用)
  • 原文地址:https://www.cnblogs.com/zhangyang17/p/3579207.html
Copyright © 2011-2022 走看看