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
  • 相关阅读:
    Linux常用命令
    Docker常用命令
    Google操作
    JAVA实现动态二维码输出
    JAVA IDEA Debug设置
    JAVA下载https资源图片
    Nginx配置文件常用操作
    Docker安装(Ubuntu、CentOS)
    SpringBoot启动脚本
    Tomcat——tomcat配置文件方式部署项目
  • 原文地址:https://www.cnblogs.com/zhangyang17/p/3579207.html
Copyright © 2011-2022 走看看