MQTT 链接

TCP属于长连接,会一直占用服务器资源。http即用即断,占用资源短
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状态,完成三次握手。
我的理解大概是:
1: C端发送一个包给S端,告诉S端我要连接你
2: S端收到C端的连接请求,判断下自身资源等情况,然后返回一个包个C端,允许你连接
3: C端收到包,知道S端已就绪,返回一个确认包给S,双方准备就绪

四次挥手理解:
1: C端发送一个包给S端,告诉S端我要断开连接了
2:S端收到包,返回一个确认包给C端,表示我知道了
3:S端准备就绪后,发送一个包给C端,告诉C我准备断开连接了
4 C发送一个确认包给S,表示我知道你断开了。
为什么有4次挥手?2、3步不可以合并的原因是,2步发生时S端可能正在往C端发送数据,所以只能等数据发送完毕再执行3.

TCP的底层机制能保证发送方和接收方的数据一致。
发送数据时,即使只发送一个字节的数据,也会包装上ip,端口等信息而导致数据变大,为了效率,数据很可能会先存储在一个缓存池中,等待一小会一起发送。数据量大时因为网络问题,如网络拥堵等,包的大小也有各种限制,会导致一段数据拆分成若干小包发送,数据粘包现象由此产生。 接收数据时需要按照一定的格式进行拆包处理,还原成发送时的样子。现在普遍是采用header标示,按照约定好的格式统一发送和接受处理。

以MQTT框架为例。
发送数据组装如下

- (NSData *)wireFormat {
    NSMutableData *buffer = [[NSMutableData alloc] init];
    UInt8 header; // header共一个字节,即8位
    header = (self.type & 0x0f) << 4; //  左边4位存储type,
    if (self.dupFlag) { // 第5位存储dupFlag
        header |= 0x08;
    }
    header |= (self.qos & 0x03) << 1; // 第6、7位存储qos,因为qos有三种状态,需要2位才能存储的下
    if (self.retainFlag) {// 第8位存储retainFlag
        header |= 0x01;
    }
    [buffer appendBytes:&header length:1];
    [buffer appendVariableLength:self.data.length];
    if (self.data != nil) {
        [buffer appendData:self.data];
    }
    return buffer;
}

一个完整的包由header(1个字节),包长(一个或多个字节),包内容组成, 例:心跳包为\xc0,占据两个字节,二进制为1100 0000 0000 0000,前面8位一个字节为header,后面八位为包长. 其中header左边4位为c,对应为心跳包MQTTPingreq类型。 包长一个字节,内容为0,表示这个包没有内容

数据解析如下

- (void)decodeMessage:(NSData *)data {
   NSInputStream *stream = [NSInputStream inputStreamWithData:data];
   CFReadStreamRef readStream = (__bridge CFReadStreamRef)stream;
   CFReadStreamSetDispatchQueue(readStream, self.queue);
   [self openStream:stream];
}

- (void)openStream:(NSInputStream *)stream {
   [self.streams addObject:stream];
   stream.delegate = self;
   // 解析流的时候先用数组将流装起来,防止上一个流还没解析完成。只有一个流时才开始解析
   // 若数组中有多个流,则当第一个流解析完成,再解析第二个,类似串形队列
   if (self.streams.count == 1) {
       [stream open];
   }
}

- (void)stream:(NSStream *)sender handleEvent:(NSStreamEvent)eventCode {
   MQTTDecoder *strongDecoder = self;
   (void)strongDecoder;
   NSInputStream *stream = (NSInputStream *)sender;
   if (eventCode & NSStreamEventHasBytesAvailable) {// 流中还有字节时会来到这里
       // self.state 默认值是MQTTDecoderStateDecodingHeader,先解析header
       if (self.state == MQTTDecoderStateDecodingHeader) {
           UInt8 buffer;
           NSInteger n = [stream read:&buffer maxLength:1];
           if (n == -1) {
               self.state = MQTTDecoderStateConnectionError;
               [self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
           } else if (n == 1) {
               // header是一个字节,解析完会进入这里,设置self.state为MQTTDecoderStateDecodingLength,解析包长
               self.length = 0;
               self.lengthMultiplier = 1;
               self.state = MQTTDecoderStateDecodingLength;
               self.dataBuffer = [[NSMutableData alloc] init];
               [self.dataBuffer appendBytes:&buffer length:1];
               self.offset = 1;
           }
       }
       while (self.state == MQTTDecoderStateDecodingLength) {// 解析包长
           UInt8 digit;
           NSInteger n = [stream read:&digit maxLength:1];
           if (n == -1) {
               self.state = MQTTDecoderStateConnectionError;
               [self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
               break;
           } else if (n == 0) {
               break;
           }
           [self.dataBuffer appendBytes:&digit length:1];
           self.offset++;
           self.length += ((digit & 0x7f) * self.lengthMultiplier);
           if ((digit & 0x80) == 0x00) {
               // 0x80为1000 0000,代码来到这里表示digit的第一位为0,包长解析完成,设置self.state为MQTTDecoderStateDecodingData,退出循环
               self.state = MQTTDecoderStateDecodingData;
           } else {
               self.lengthMultiplier *= 128;
           }
       }
       if (self.state == MQTTDecoderStateDecodingData) {// 解析包实际数据
           if (self.length > 0) {
               NSInteger n, toRead;
               UInt8 buffer[768];
               toRead = self.length + self.offset - self.dataBuffer.length;
               if (toRead > sizeof buffer) {
                   toRead = sizeof buffer;
               }
               n = [stream read:buffer maxLength:toRead];
               if (n == -1) {
                   self.state = MQTTDecoderStateConnectionError;
                   [self.delegate decoder:self handleEvent:MQTTDecoderEventConnectionError error:stream.streamError];
               } else {
                   [self.dataBuffer appendBytes:buffer length:n];
               }
           }
           if (self.dataBuffer.length == self.length + self.offset) {
               // 因为tcp保证发送方和接收方会完全一致,所以肯定会来到这里,代表一条发送方的完整数据解析完成
               // 重新设置self.state为MQTTDecoderStateDecodingHeader,则会重新解析header
               // 因为粘包问题,这里有可能这个流还没有解析完成,因为流中还有数据,这个代理方法还会再次被调用,重走流程
               [self.delegate decoder:self didReceiveMessage:self.dataBuffer];
               self.dataBuffer = nil;
               self.state = MQTTDecoderStateDecodingHeader;
           }
       }
   }
   if (eventCode & NSStreamEventEndEncountered) {// 一个流解析完成,关闭流。若数组中还有流则取出第一个流开始解析
       if (self.streams) {
           [stream setDelegate:nil];
           [stream close];
           [self.streams removeObject:stream];
           if (self.streams.count) {
               NSInputStream *stream = (self.streams)[0];
               [stream open];
           }
       }
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容