LCWechat -- socket编解码

前言

服务器传输协议协议:  
基于netty LengthFieldBasedFrameDecoder(100000000,0,4,0,4) 
总长度 = 4byte + 包体内容
接收二进制的json字符串

正文

思路:
1.在发送数据包前, 拼接4个字节带有包体内容长度的数据.
2.在接受数时, 需要考虑 粘包和半包的情况.根据包头长度,判断包
  的完整性.将完整的包解析出去,不完整的等待下一次的数据拼接.

设计

1.采用面向协议的方式编码与解码
2.自定义编码器和解码器, 遵守协议, 实现协议中的方法
3.基于GCDAsyncSocket, 封装需要用到的方法
文件结构
52D4F31C-68DA-4E9E-A667-70EC29032BF0.png

分块讲解

协议 - LCSocketCoderProtocol
1.编码协议:LCSocketEncoderProtocol
2.解码协议:LCSocketDecoderProtocol
3.编码完成后的输出协议:LCSocketEncoderOutputProtocol
4.解码完成后的输出协议:LCSocketDecoderOutputProtocol
编码器 - LCSocketEncoder
1. 遵守编码协议
2.实现协议中的方法:
- (void)encode:(id)object output (id<LCSocketEncoderOutputProtocol>)output
其中, 输出output遵守 编码输出协议
编码器 - LCSocketDecoder
1. 遵守解码协议
2.实现协议中的方法:
- (void)decode:(id)object output:(id<LCSocketDecoderOutputProtocol>)output
其中, 输出output遵守 解码输出协议
套接字 - LCBaseSocket
1. 基于GCDAsyncSocket
2.单例化, 提供需要给外界访问的接口,如:"重连", "断开连接". "是否正在连接"等
3.设置代理属性

编码器代码详解 - LCSocketEncoder

1.判断数据格式是否可解析为json
 if (![NSJSONSerialization isValidJSONObject:object]) {
        [output didEndEncode:nil error:[NSError errorWithDomain:@"数据不能解析为json" code:-1 userInfo:nil]];
        return;
    }

2.将json数据编码为NSData
 NSError *error = nil;
 NSData *contentData = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:&error];
 NSString *contentStr = [[NSString alloc] initWithData:contentData encoding:NSUTF8StringEncoding];
 contentData = [contentStr dataUsingEncoding:NSUTF8StringEncoding];

3.判断编码后的数据长度
 if (contentData.length > 1000000 - countOfLengthByte) {
        [output didEndEncode:nil error:[NSError errorWithDomain:@"encoder的数据太长" code:-1 userInfo:nil]];
        return;
    }

4.将数据长度, 拼接到包头
 NSUInteger contentDataLength = contentData.length;
 NSData *headData = [self dataForLength:contentDataLength byteCount:countOfLengthByte reverse:NO];
    
 NSMutableData *data = [NSMutableData data];
 [data appendData:headData];
 [data appendData:contentData];

5.分发数据
 [output didEndEncode:data error:nil];

解码器代码详解 - LCSocketDecoder

1.判断数据是否为NSData类型
if (![object isKindOfClass:[NSData class]]) {
        [output didEndDecode:nil error:[NSError errorWithDomain:@"当前数据类型非NSData" code:-1 userInfo:nil]];
        return;
    }

2.初始化, 并截取包头数据
 self.needAppend = NO;
 NSData *packetData = object;
 [self.tempData appendData:packetData];
 packetData = [self.tempData copy];
    
 NSData *headerData = [packetData subdataWithRange:NSMakeRange(0, countOfLengthByte)];
 NSUInteger contentLength = [self lengthForData:headerData reverse:YES];

3.判断是否粘包
while (packetData.length >= contentLength) { }
  3.1 判断包的长度
  if (contentLength > 1000000 - countOfLengthByte) { // 服务器定的...
            [output didEndDecode:nil error:[NSError errorWithDomain:@"数据太长" code:-1 userInfo:nil]];
            return;

        }
  
  3.2截取包体内容
  NSData *contentData = [packetData subdataWithRange:NSMakeRange(countOfLengthByte, contentLength)];

  3.3 输出一个完整的包, 截取剩余的包
   [output didEndDecode:contentData error:nil];
  packetData = [packetData subdataWithRange:NSMakeRange(contentLength + countOfLengthByte , packetData.length - contentLength - countOfLengthByte)];

  3.4 判断包头长度, 如果小于规定的, 则跳出循环, 等待拼接
    if (packetData.length < countOfLengthByte) {
            if (self.tempData.length != 0) {
                [self.tempData resetBytesInRange:NSMakeRange(0, self.tempData.length)];
                [self.tempData setLength:0];
                self.needAppend = YES;
            }
            break;
        }

  3.5 截取包头的数据,计算长度
   headerData = [packetData subdataWithRange:NSMakeRange(0, countOfLengthByte)];
  contentLength = [self lengthForData:headerData reverse:YES];

  3.6.只要执行了while, 将tempData的数据清空, 拼接新的数据
  if (self.tempData.length != 0) {
            [self.tempData resetBytesInRange:NSMakeRange(0, self.tempData.length)];
            [self.tempData setLength:0];
        }
        
        self.needAppend = YES;

一些小细节

1.计算根据NSData包头的长度- lengthForData

扫盲
NSData 中的length为NSUInteger类型
接下来我们看看 NSUInteger为何物

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

目前一般都为64位操作系统, 所以 NSUInteger 就是 unsigned long 占了8bits
1字节(byte) = 8bits,两者换算是1:8的关系。
略带:一个汉字占了两个字节
- (NSUInteger)lengthForData:(NSData *)data
{
    NSUInteger dataLen = data.length;
    NSUInteger length = 0;
    
    int offset = 0;
    while (offset < dataLen) {
        NSUInteger tempVal = 0;
        [data getBytes:&tempVal range:NSMakeRange(offset, 1)];
        length += (tempVal << (8 * offset));
        offset++;
    }
    
    return length;
}

1.将数据的高低位, 更改为低位在前,高位在后,如:
<00000045> --> <45000000>数据以16进制的形式展示,
验证:利用计算器转换为十进制 等于 69.后附图.

2.利用NSData 中的:
  - (void)getBytes:(void *)buffer range:(NSRange)range
方法获取每一个字节

3.(注意当前为:低位在前,高位在后)
将每一个字节, 根据字节所属的范围,统一往左位移相应的 8 (1字节(byte) = 8bits)的倍数, 
也就是说,将后面高位的字节,统一处理为低位的字节, 将统一格式的字节, 相加.
利用%zd 输出每一个字节的 十进制的大小.进行检验.
4AE2B600-2ACB-40F7-958C-35D9B8A9EEA1.png
CFD0FD1D-FD26-474F-831C-C3902E18DCBC.png
ACF58783-249F-4996-B6F5-ADD3514175AF.png

2.高低位互换-dataWithReverse

- (NSData *)dataWithReverse:(NSData *)data
{
    NSMutableData *dstData = [[NSMutableData alloc] initWithData:data];
    NSUInteger count = data.length / 2;
    
    for (NSUInteger i = 0; i < count; i++) {
        
        NSRange head = NSMakeRange(i, 1);
        NSRange end = NSMakeRange(data.length - i - 1, 1);
        
        NSData *headData = [data subdataWithRange:head];
        NSData *endData = [data subdataWithRange:end];
        
        [dstData replaceBytesInRange:head withBytes:endData.bytes];
        [dstData replaceBytesInRange:end withBytes:headData.bytes];
    }
    
    return dstData;
}

1.例用NSMutableData 字节互换 的方法
- (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)bytes;

2.遍历数据
从收尾开始,依次将字节互换, 一般包头所占字节都不会太多, 所以只需遍历几次就可以完成,遍历的次数为字节总数减半

3.将长度转为占固定字节数的NSData - dataForLength

  NSMutableData *valData = [NSMutableData data];
    NSUInteger templen = length;
    
    int offset = 0;
    while (offset < byteCount) {
        unsigned char valChar = 0xff & templen;
        [valData appendBytes:&valChar length:1];

        templen = templen >> 8;
        offset++;
    }

    return valData;

1.利用:NSMutableData 的方法
- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;

2.将数据   & 0xff 
0xff是十六进制FF的表示方法,因为一个十六进制数字转换成二进制是四位,即F=1111,所以0xff占用一个字节, 8bits
&符表示的是按位数进行与(同为1的时候返回1,否则返回0)
保留后7位,高位清零,避免符号位扩展:

3.while循环的说明:
首先, 0xff & templen, 只保留最低的字节, 获取到最低位的字节, 利用appendBytes, 将此字节添加进去, 
进而将templen往右移8位, 也就是剔除刚才计算过的字节, 获取第二个字节
,将第二个字节添加进去.依次往后直到将所有的包头字节添加完毕
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 摘要 该配置文件定义了支持高质量音频分发所需的Bluetooth®设备的要求。这些要求以终端用户服务的方式表达,并...
    公子小水阅读 9,543评论 0 4
  • 史上最全的iOS面试题及答案 iOS面试小贴士———————————————回答好下面的足够了----------...
    Style_伟阅读 2,346评论 0 35
  • ———————————————回答好下面的足够了---------------------------------...
    恒爱DE问候阅读 1,712评论 0 4
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,968评论 0 7
  • 感情是一种很奇妙的事物,少许人控制的住心中涌起的那股激动,那是一种像微生物一般的让人很难琢磨的事情。为何不放手一搏...
    时光里的杯杯阅读 214评论 0 0