美文网首页
iOS 使用Protobuf完成Socket通信(以及收到消息的

iOS 使用Protobuf完成Socket通信(以及收到消息的

作者: 秀_ba75 | 来源:发表于2020-05-20 15:25 被阅读0次

Protobuf的配置网上有很多教程,这里就不说了
这里主要讲 Socket 的使用
我用的是 CocoaAsyncSocket
首先用 Cocoapods 导入 CocoaAsyncSocket pod 'CocoaAsyncSocket'
然后就是初始化,连接服务器,这里的服务器地址和端口号问后台要

 // 连接服务器
   self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
   NSError *error = nil;
  self.connected = [self.clientSocket connectToHost:服务器地址 onPort:端口号 viaInterface:nil withTimeout:-1 error:&error];

   if(self.connected)
   {
       [MBProgressHUD showMessage:@"客户端创建连接"];
   }
  else
  {
       self.connected = NO;
       [MBProgressHUD showMessage:@"客户端未创建连接"];
    }

接下来是用到的代理方法

//连接主机对应端口
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{

    // 连接上服务器
    [MBProgressHUD showSuccess:@"连接成功"];
//这里是一个定时器,多久像后台发送一次心跳
    [self addTimer];
    //    连接后,可读取服务器端的数据
    [self.clientSocket readDataWithTimeout:- 1 tag:0];
    self.connected = YES;
}

// 客户端socket断开连接
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    
    //断开连接后的处理
}

// 收到消息(收到消息这个地方用到了拆包,因为我们的消息比较多,有的好像是不需要的,这个也问后台人员)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    
    [self.receiveData appendData:data];
    
    //读取data的头部占用字节 和 从头部读取内容长度
    //验证结果:数据比较小时头部占用字节为1,数据比较大时头部占用字节为2
    int32_t headL = 0;
    int32_t contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
    
    if (contentL < 1){
        [sock readDataWithTimeout:-1 tag:0];
        return;
    }
    
    //拆包情况下:继续接收下一条消息,直至接收完这条消息所有的拆包,再解析
    if (headL + contentL > self.receiveData.length){
        [sock readDataWithTimeout:-1 tag:0];
        return;
    }
    
    //当receiveData长度不小于第一条消息内容长度时,开始解析receiveData
    [self parseContentDataWithHeadLength:headL withContentLength:contentL];
    [sock readDataWithTimeout:-1 tag:0];
}

下面这些是上面的一些调用的方法

拆包

/** 解析二进制数据:NSData --> 自定义模型对象 */
- (void)parseContentDataWithHeadLength:(int32_t)headL withContentLength:(int32_t)contentL{
    
    NSRange range = NSMakeRange(0, headL + contentL);   //本次解析data的范围
    NSData *data = [self.receiveData subdataWithRange:range]; //本次解析的data
    //GPBCodedInputStream是protobuf的类
    GPBCodedInputStream *inputStream = [GPBCodedInputStream streamWithData:data];
    //    NSLog(@"_________%d",[inputStream readTag]);
    NSError *error;
    Message *obj = [Message parseDelimitedFromCodedInputStream:inputStream extensionRegistry:nil error:&error];
    
    if (!error){
        if (obj) [self saveReceiveInfo:obj];  //保存解析正确的模型对象
        [self.receiveData replaceBytesInRange:range withBytes:NULL length:0];  //移除已经解析过的data
    }
    
    if (self.receiveData.length < 1) return;
    
    //对于粘包情况下被合并的多条消息,循环递归直至解析完所有消息
    headL = 0;
    contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
    
    if (headL + contentL > self.receiveData.length) return; //实际包不足解析,继续接收下一个包
    
    [self parseContentDataWithHeadLength:headL withContentLength:contentL]; //继续解析下一条
}

/** 获取data数据的内容长度和头部长度: index --> 头部占用长度 (头部占用长度1-4个字节) */
- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{
    
    int8_t tmp = [self readRawByte:data headIndex:index];
    
    if (tmp >= 0) return tmp;
    
    int32_t result = tmp & 0x7f;
    if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
        result |= tmp << 7;
    } else {
        result |= (tmp & 0x7f) << 7;
        if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
            result |= tmp << 14;
        } else {
            result |= (tmp & 0x7f) << 14;
            if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
                result |= tmp << 21;
            } else {
                result |= (tmp & 0x7f) << 21;
                result |= (tmp = [self readRawByte:data headIndex:index]) << 28;
                if (tmp < 0) {
                    for (int i = 0; i < 5; i++) {
                        if ([self readRawByte:data headIndex:index] >= 0) {
                            return result;
                        }
                    }
                    
                    result = -1;
                }
            }
        }
    }
    return result;
}
/** 处理解析出来的信息 */
- (void)saveReceiveInfo:(Message *)obj{
    //...
}
/** 读取字节 */
- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{
    
    if (*index >= data.length) return -1;
    
    *index = *index + 1;
    
    return ((int8_t *)data.bytes)[*index - 1];
}

定时器

- (void)addTimer
{
    //    NSLog(@"定时器开启时间%@",[self getCurrentSecond]);
    // 长连接定时器
    self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
    // 把定时器添加到当前运行循环,并且调为通用模式
    [[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];
}
// 心跳连接
- (void)longConnectToSocket{
    NSLog(@"心跳发送%s",__func__);
//这个Message就是我自己创建的Protobuf,大家用自己的就好
    Message *msg = [[Message alloc] init];
    SocketModel *model = [SocketModel new];
    model.msg = @"cbf0524e-5da9-4385-89ed-3cfc25df5dd9";
    msg.data_p = [model mj_JSONString];
    msg.headType = 3;
//注意这个data数据的生成方式
    NSData *data = [msg delimitedData];
    [self.clientSocket writeData:data withTimeout:- 1 tag:0];
}

差不多全贴出来了,以上这些拿过来应该就可以直接用的

相关文章

网友评论

      本文标题:iOS 使用Protobuf完成Socket通信(以及收到消息的

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