iOS 使用GCDAsyncSocket及粘包、半包處理

iOS開發(fā)中可以使用開源庫CocoaAsyncSocket簡化socket開發(fā)

**1.連接socket **

//創(chuàng)建一個(gè)TCP服務(wù) 連接到服務(wù)器
- (void)createTcpSocket {
    if (self.asyncSocket==nil) {
        self.asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    if (self.asyncSocket.isConnected) {
    } else {
        NSError *error;
        [self.asyncSocket connectToHost:_socketHost onPort:_socketPort withTimeout:-1 error:&error];
        if (error) {
            NSLog(@"%@",error);
        }
    }
}

2.實(shí)現(xiàn)socket代理方法

//已經(jīng)連接到服務(wù)器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"連接成功");
}
// 連接斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
   NSLog(@"連接斷開");
}
//消息發(fā)送成功 代理函數(shù) 向服務(wù)器 發(fā)送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    NSLog(@"消息發(fā)送成功");
}
//已經(jīng)接收服務(wù)器返回來的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { 
    NSLog(@"接收到服務(wù)器返回的數(shù)據(jù)");
}

3.向服務(wù)器發(fā)送消息

[self.asyncSocket writeData:self.message withTimeout:-1 tag:0];

以上只是簡單的介紹CocoaAsyncSocket的使用,下面結(jié)合實(shí)際開發(fā)中的需求來講解開發(fā)中的關(guān)鍵點(diǎn),比如 tag 用來干嘛???

在開發(fā)中前端和后端會(huì)約定一個(gè)固定的數(shù)據(jù)(消息)格式,按照這個(gè)格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了 粘包 掉包 的問題,數(shù)據(jù)不完整時(shí)也能獲知數(shù)據(jù)的缺失。

舉個(gè)栗子,如下表格是規(guī)定好的消息格式
起始符 目標(biāo)地址 原地址 數(shù)據(jù)長度 發(fā)送/接收 的數(shù)據(jù) 檢驗(yàn)符
0x02(1Byte) dest(2Byte) src(2Byte) dataLength(2Byte) data (數(shù)據(jù)) CRC校驗(yàn) (1Byte)

注:以上的消息格式分成3塊,數(shù)據(jù)和校驗(yàn)符在這里稱為身體部分
1.消息頭部:起始符 + 目標(biāo)地址 + 原地址 + 數(shù)據(jù)長度 = 7Byte,(頭部包含了那么多信息,并且長度是固定的,所以我們接收消息的時(shí)候,要先從頭部開始入手)
2.消息體:要發(fā)送或者接收到的數(shù)據(jù),長度為dataLength
3.校驗(yàn)符:用來檢驗(yàn)接收的數(shù)據(jù)是否完整一致(開發(fā)中可能沒有)

** 為了能夠在收到消息時(shí),先獲取到頭部信息 ,我們需要用到 tag **

//固定的頭部長度
//起始符(1Byte) + 目標(biāo)地址(2byte) + 源地址(2byte) + 應(yīng)用層數(shù)據(jù)長度(2byte) = 7Byte
#define KPacketHeaderLength 7
typedef NS_ENUM(NSInteger ,KReadDataType){
    TAG_FIXED_LENGTH_HEADER = 10,//消息頭部tag
    TAG_RESPONSE_BODY = 11//消息體tag
};

** 所以在socket連接成功之后應(yīng)該這樣寫 **

//已經(jīng)連接到服務(wù)器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"連接成功");
    // -1不超時(shí)一直讀取 等待數(shù)據(jù),先讀取頭部信息長度為 KPacketHeaderLength
    // tag為頭部消息tag,這個(gè)在接收到數(shù)據(jù)時(shí),用來區(qū)分此次讀取的是頭部數(shù)據(jù)還是消息體數(shù)據(jù)
    [self.asyncSocket readDataToLength:KPacketHeaderLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
}

** 在接受到服務(wù)器的數(shù)據(jù)時(shí) **

//已經(jīng)接收服務(wù)器返回來的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { 
    NSLog(@"接收到服務(wù)器返回的數(shù)據(jù)");
    // 根據(jù)tag來做不同的操作
    switch (tag) {
        case TAG_FIXED_LENGTH_HEADER:
        {
            self.headData = data;
            Byte *bytes = (Byte *)[data bytes];

            //身體長度 = 消息體 + 1Byte的校驗(yàn)碼
            self.bodyLength =  [self readShort:bytes location:5] + 1;

            // 從數(shù)據(jù)緩沖區(qū)讀取完整的身體部分?jǐn)?shù)據(jù),此時(shí)tag變成了TAG_RESPONSE_BODY
            [self.asyncSocket readDataToLength:self.bodyLength withTimeout:-1 tag:TAG_RESPONSE_BODY];
        }
            break;
        case TAG_RESPONSE_BODY:{

            //如果當(dāng)前讀取出來的數(shù)據(jù)長度沒有達(dá)到完整包身體的長度,則包不完整(則根據(jù)當(dāng)前接收的數(shù)據(jù)長度,和身體長度比較,繼續(xù)讀取兩者相差的數(shù)據(jù)長度)

            //讀取完身體數(shù)據(jù),開始校驗(yàn),校驗(yàn)成功,則展示數(shù)據(jù)并且,開始等待下一次讀取數(shù)據(jù),tag變成TAG_FIXED_LENGTH_HEADER
            [self.asyncSocket readDataToLength:KPacketHeaderLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
        }
            break;
        default:
            break;
    }
}

- (short)readShort:(Byte *)bytes location:(int)location {
    return OSReadLittleInt16(bytes, location);
}

這些只是開發(fā)中可能會(huì)遇到的點(diǎn),實(shí)際中還有很多細(xì)節(jié)要處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20170615/19529.html 參考:h...
    F麥子閱讀 4,025評(píng)論 3 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,841評(píng)論 25 708
  • iPhone的標(biāo)準(zhǔn)推薦是CFNetwork 庫編程,其封裝好的開源庫是 cocoa AsyncSocket庫,用它...
    Ethan_Struggle閱讀 2,273評(píng)論 2 12
  • 經(jīng)常在書中看到,許多人因?yàn)楦星椴荒茜娗榫鞂伲谝黄鸬幕橐觯钟卸嗌俨皇且驗(yàn)閻矍椤?我,27歲,已婚。 我出生在一...
    揚(yáng)揚(yáng)灑灑閱讀 146評(píng)論 0 0
  • 圣經(jīng)中沒有記載耶穌此日的言行。在這一天里,主耶穌什么都沒有去做,祂把自己擺在父神的面前,靜心靈修,藉著與父...
    木木_77閱讀 2,305評(píng)論 0 1