LCWechat -- socket編解碼

前言

服務(wù)器傳輸協(xié)議協(xié)議:  
基于netty LengthFieldBasedFrameDecoder(100000000,0,4,0,4) 
總長(zhǎng)度 = 4byte + 包體內(nèi)容
接收二進(jìn)制的json字符串

正文

思路:
1.在發(fā)送數(shù)據(jù)包前, 拼接4個(gè)字節(jié)帶有包體內(nèi)容長(zhǎng)度的數(shù)據(jù).
2.在接受數(shù)時(shí), 需要考慮 粘包和半包的情況.根據(jù)包頭長(zhǎng)度,判斷包
  的完整性.將完整的包解析出去,不完整的等待下一次的數(shù)據(jù)拼接.

設(shè)計(jì)

1.采用面向協(xié)議的方式編碼與解碼
2.自定義編碼器和解碼器, 遵守協(xié)議, 實(shí)現(xiàn)協(xié)議中的方法
3.基于GCDAsyncSocket, 封裝需要用到的方法
文件結(jié)構(gòu)
52D4F31C-68DA-4E9E-A667-70EC29032BF0.png

分塊講解

協(xié)議 - LCSocketCoderProtocol
1.編碼協(xié)議:LCSocketEncoderProtocol
2.解碼協(xié)議:LCSocketDecoderProtocol
3.編碼完成后的輸出協(xié)議:LCSocketEncoderOutputProtocol
4.解碼完成后的輸出協(xié)議:LCSocketDecoderOutputProtocol
編碼器 - LCSocketEncoder
1. 遵守編碼協(xié)議
2.實(shí)現(xiàn)協(xié)議中的方法:
- (void)encode:(id)object output (id<LCSocketEncoderOutputProtocol>)output
其中, 輸出output遵守 編碼輸出協(xié)議
編碼器 - LCSocketDecoder
1. 遵守解碼協(xié)議
2.實(shí)現(xiàn)協(xié)議中的方法:
- (void)decode:(id)object output:(id<LCSocketDecoderOutputProtocol>)output
其中, 輸出output遵守 解碼輸出協(xié)議
套接字 - LCBaseSocket
1. 基于GCDAsyncSocket
2.單例化, 提供需要給外界訪問的接口,如:"重連", "斷開連接". "是否正在連接"等
3.設(shè)置代理屬性

編碼器代碼詳解 - LCSocketEncoder

1.判斷數(shù)據(jù)格式是否可解析為json
 if (![NSJSONSerialization isValidJSONObject:object]) {
        [output didEndEncode:nil error:[NSError errorWithDomain:@"數(shù)據(jù)不能解析為json" code:-1 userInfo:nil]];
        return;
    }

2.將json數(shù)據(jù)編碼為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.判斷編碼后的數(shù)據(jù)長(zhǎng)度
 if (contentData.length > 1000000 - countOfLengthByte) {
        [output didEndEncode:nil error:[NSError errorWithDomain:@"encoder的數(shù)據(jù)太長(zhǎng)" code:-1 userInfo:nil]];
        return;
    }

4.將數(shù)據(jù)長(zhǎng)度, 拼接到包頭
 NSUInteger contentDataLength = contentData.length;
 NSData *headData = [self dataForLength:contentDataLength byteCount:countOfLengthByte reverse:NO];
    
 NSMutableData *data = [NSMutableData data];
 [data appendData:headData];
 [data appendData:contentData];

5.分發(fā)數(shù)據(jù)
 [output didEndEncode:data error:nil];

解碼器代碼詳解 - LCSocketDecoder

1.判斷數(shù)據(jù)是否為NSData類型
if (![object isKindOfClass:[NSData class]]) {
        [output didEndDecode:nil error:[NSError errorWithDomain:@"當(dāng)前數(shù)據(jù)類型非NSData" code:-1 userInfo:nil]];
        return;
    }

2.初始化, 并截取包頭數(shù)據(jù)
 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 判斷包的長(zhǎng)度
  if (contentLength > 1000000 - countOfLengthByte) { // 服務(wù)器定的...
            [output didEndDecode:nil error:[NSError errorWithDomain:@"數(shù)據(jù)太長(zhǎng)" code:-1 userInfo:nil]];
            return;

        }
  
  3.2截取包體內(nèi)容
  NSData *contentData = [packetData subdataWithRange:NSMakeRange(countOfLengthByte, contentLength)];

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

  3.4 判斷包頭長(zhǎng)度, 如果小于規(guī)定的, 則跳出循環(huán), 等待拼接
    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 截取包頭的數(shù)據(jù),計(jì)算長(zhǎng)度
   headerData = [packetData subdataWithRange:NSMakeRange(0, countOfLengthByte)];
  contentLength = [self lengthForData:headerData reverse:YES];

  3.6.只要執(zhí)行了while, 將tempData的數(shù)據(jù)清空, 拼接新的數(shù)據(jù)
  if (self.tempData.length != 0) {
            [self.tempData resetBytesInRange:NSMakeRange(0, self.tempData.length)];
            [self.tempData setLength:0];
        }
        
        self.needAppend = YES;

一些小細(xì)節(jié)

1.計(jì)算根據(jù)NSData包頭的長(zhǎng)度- 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位操作系統(tǒng), 所以 NSUInteger 就是 unsigned long 占了8bits
1字節(jié)(byte) = 8bits,兩者換算是1:8的關(guān)系。
略帶:一個(gè)漢字占了兩個(gè)字節(jié)
- (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.將數(shù)據(jù)的高低位, 更改為低位在前,高位在后,如:
<00000045> --> <45000000>數(shù)據(jù)以16進(jìn)制的形式展示,
驗(yàn)證:利用計(jì)算器轉(zhuǎn)換為十進(jìn)制 等于 69.后附圖.

2.利用NSData 中的:
  - (void)getBytes:(void *)buffer range:(NSRange)range
方法獲取每一個(gè)字節(jié)

3.(注意當(dāng)前為:低位在前,高位在后)
將每一個(gè)字節(jié), 根據(jù)字節(jié)所屬的范圍,統(tǒng)一往左位移相應(yīng)的 8 (1字節(jié)(byte) = 8bits)的倍數(shù), 
也就是說,將后面高位的字節(jié),統(tǒng)一處理為低位的字節(jié), 將統(tǒng)一格式的字節(jié), 相加.
利用%zd 輸出每一個(gè)字節(jié)的 十進(jìn)制的大小.進(jìn)行檢驗(yàn).
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 字節(jié)互換 的方法
- (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)bytes;

2.遍歷數(shù)據(jù)
從收尾開始,依次將字節(jié)互換, 一般包頭所占字節(jié)都不會(huì)太多, 所以只需遍歷幾次就可以完成,遍歷的次數(shù)為字節(jié)總數(shù)減半

3.將長(zhǎng)度轉(zhuǎn)為占固定字節(jié)數(shù)的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.將數(shù)據(jù)   & 0xff 
0xff是十六進(jìn)制FF的表示方法,因?yàn)橐粋€(gè)十六進(jìn)制數(shù)字轉(zhuǎn)換成二進(jìn)制是四位,即F=1111,所以0xff占用一個(gè)字節(jié), 8bits
&符表示的是按位數(shù)進(jìn)行與(同為1的時(shí)候返回1,否則返回0)
保留后7位,高位清零,避免符號(hào)位擴(kuò)展:

3.while循環(huán)的說明:
首先, 0xff & templen, 只保留最低的字節(jié), 獲取到最低位的字節(jié), 利用appendBytes, 將此字節(jié)添加進(jìn)去, 
進(jìn)而將templen往右移8位, 也就是剔除剛才計(jì)算過的字節(jié), 獲取第二個(gè)字節(jié)
,將第二個(gè)字節(jié)添加進(jìn)去.依次往后直到將所有的包頭字節(jié)添加完畢
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,165評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,435評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,943評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,175評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,713評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,599評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,788評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,034評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評(píng)論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,408評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,747評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 摘要 該配置文件定義了支持高質(zhì)量音頻分發(fā)所需的Bluetooth?設(shè)備的要求。這些要求以終端用戶服務(wù)的方式表達(dá),并...
    公子小水閱讀 9,906評(píng)論 0 4
  • 史上最全的iOS面試題及答案 iOS面試小貼士———————————————回答好下面的足夠了----------...
    Style_偉閱讀 2,372評(píng)論 0 35
  • ———————————————回答好下面的足夠了---------------------------------...
    恒愛DE問候閱讀 1,742評(píng)論 0 4
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 1,996評(píng)論 0 7
  • 感情是一種很奇妙的事物,少許人控制的住心中涌起的那股激動(dòng),那是一種像微生物一般的讓人很難琢磨的事情。為何不放手一搏...
    時(shí)光里的杯杯閱讀 222評(píng)論 0 0