iOS GCDAsyncSocket简单使用

作者: 尤先森 | 来源:发表于2019-03-28 22:38 被阅读0次

    接上篇文章用原生代码写socket,现在这篇文章主要介绍GCDAsyncSocket的使用,后续将写关于GCDAsyncSocket的源码分析。

    GCDAsyncSocket使用

    1. 通过pod导入 pod 'CocoaAsyncSocket'
    2. 导入头文件 #import <GCDAsyncSocket.h>
    3. 声明变量 遵循代理
    @interface ViewController ()<GCDAsyncSocketDelegate>
    @property (nonatomic, strong) GCDAsyncSocket *socket;
    @end
    

    4.连接socket

    #pragma mark - 连接socket
    - (IBAction)didClickConnectSocket:(id)sender {
        // 创建socket
        if (self.socket == nil)
            // 并发队列,这个队列将影响delegate回调,但里面是同步函数!保证数据不混乱,一条一条来
            // 这里最好是写自己并发队列
            self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
        // 连接socket
        if (!self.socket.isConnected){
            NSError *error;
            [self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
            if (error) NSLog(@"%@",error);
        }
    }
    

    5.发送消息

    - (IBAction)didClickSendAction:(id)sender {
        
        NSData *data = [@"发送的消息内容" dataUsingEncoding:NSUTF8StringEncoding];
        [self.socket writeData:data withTimeout:-1 tag:10086];
    }
    

    6.重连

    - (IBAction)didClickReconnectAction:(id)sender {
        // 创建socket
        if (self.socket == nil)
            self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
        // 连接socket
        if (!self.socket.isConnected){
            NSError *error;
            [self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
            if (error) NSLog(@"%@",error);
        }
    }
    

    7.关闭socket

    - (IBAction)didClickCloseAction:(id)sender {
        [self.socket disconnect];
        self.socket = nil;
    }
    

    GCDAsyncSocketDelegate

    //已经连接到服务器
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
        NSLog(@"连接成功 : %@---%d",host,port);
        //连接成功或者收到消息,必须开始read,否则将无法收到消息,
        //不read的话,缓存区将会被关闭
        // -1 表示无限时长 ,永久不失效
        [self.socket readDataWithTimeout:-1 tag:10086];
    }
    
    // 连接断开
    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
        NSLog(@"断开 socket连接 原因:%@",err);
    }
    
    //已经接收服务器返回来的数据
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
        NSLog(@"接收到tag = %ld : %ld 长度的数据",tag,data.length);
        //连接成功或者收到消息,必须开始read,否则将无法收到消息
        //不read的话,缓存区将会被关闭
        // -1 表示无限时长 , tag
        [self.socket readDataWithTimeout:-1 tag:10086];
    }
    
    //消息发送成功 代理函数 向服务器 发送消息
    - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
        NSLog(@"%ld 发送数据成功",tag);
    }
    

    调试

    按照上篇文章调试方法,这里就不再赘述。


    image.png

    粘包、分包(拆包)

    内容摘取自

    概念

    Socket通信时会对发送的字节数据进行分包和粘包处理,属于一种Socket内部的优化机制。

    粘包:

    当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小字节数据打包成 一个整包进行发送,降低内存的消耗。

    分包:

    当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,降低内存和性能的消耗。

    例子解释
    当前发送方发送了两个包,两个包的内容如下:
    123456789
    ABCDEFGH
    

    我们希望接收方的情况是:收到两个包,第一个包为:123456789,第二个包为:ABCDEFGH。但是在粘包和分包出现的情况就达不到预期情况。

    粘包情况

    两个包在很短的时间间隔内发送,比如在0.1秒内发送了这两个包,如果包长度足够的话,那么接收方只会接收到一个包,如下:

    123456789ABCDEFGH
    
    分包情况

    假设包的长度最长设置为5字节(较极端的假设,一般长度设置为1000到1500之间),那么在没有粘包的情况下,接收方就会收到4个包,如下:

    12345
    6789
    ABCDE
    FGH
    
    处理方式

    因为存在粘包和分包的情况,所以接收方需要对接收的数据进行一定的处理,主要解决的问题有两个:

    1. 在粘包产生时,要可以在同一个包内获取出多个包的内容。
    2. 在分包产生时,要保留上一个包的部分内容,与下一个包的部分内容组合。

    处理方式:
    在数据包头部加上内容长度以及数据类型
    1.发送数据

    #pragma mark - 发送数据格式化
    - (void)sendData:(NSData *)data dataType:(unsigned int)dataType{
        NSMutableData *mData = [NSMutableData data];
        // 1.计算数据总长度 data
        unsigned int dataLength = 4+4+(int)data.length;
        // 将长度转成data
        NSData *lengthData = [NSData dataWithBytes:&dataLength length:4];
        // mData 拼接长度data
        [mData appendData:lengthData];
        
        // 数据类型 data
        // 2.拼接指令类型(4~7:指令)
        NSData *typeData = [NSData dataWithBytes:&dataType length:4];
        // mData 拼接数据类型data
        [mData appendData:typeData];
        
        // 3.最后拼接真正的数据data
        [mData appendData:data];
        NSLog(@"发送数据的总字节大小:%ld",mData.length);
        
        // 发数据
        [self.socket writeData:mData withTimeout:-1 tag:10086];
    }
    
    

    2.接收数据

    - (void)recvData:(NSData *)data{
        //直接就给他缓存起来
        [self.cacheData appendData:data];
        // 获取总的数据包大小
        // 整段数据长度(不包含长度跟类型)
        NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
        unsigned int totalSize = 0;
        [totalSizeData getBytes:&totalSize length:4];
        //包含长度跟类型的数据长度
        unsigned int completeSize = totalSize  + 8;
        //必须要大于8 才会进这个循环
        while (self.cacheData.length>8) {
            if (self.cacheData.length < completeSize) {
                //如果缓存的长度 还不如 我们传过来的数据长度,就让socket继续接收数据
                [self.socket readDataWithTimeout:-1 tag:10086];
                break;
            }
            //取出数据
            NSData *resultData = [self.cacheData subdataWithRange:NSMakeRange(8, completeSize)];
            //处理数据
            [self handleRecvData:resultData];
            //清空刚刚缓存的data
            [self.cacheData replaceBytesInRange:NSMakeRange(0, completeSize) withBytes:nil length:0];
            //如果缓存的数据长度还是大于8,再执行一次方法
            if (self.cacheData.length > 8) {
                [self recvData:nil];
            }
        }
    }
    

    心跳

    待续

    相关文章

      网友评论

        本文标题:iOS GCDAsyncSocket简单使用

        本文链接:https://www.haomeiwen.com/subject/yoifbqtx.html