本文主要介紹
CocoaAsyncSocket
的 讀/寫(xiě) 操作启绰,以及如何處理TCP粘包的問(wèn)題,CocoaAsyncSocket
基本使用的請(qǐng)查看上一篇文章Socket & CocoaAsyncSocket介紹與使用
排隊(duì) 讀/寫(xiě) 操作
CocoaAsyncSocket
庫(kù)的最佳功能之一是“排隊(duì) 讀/寫(xiě) 操作”狰晚。
-
寫(xiě)操作
: 套接字未連接,但我還是可以開(kāi)始寫(xiě)它,庫(kù)將排隊(duì)我的所有寫(xiě)操作铅碍,在套接字連接后曙蒸,它將自動(dòng)開(kāi)始執(zhí)行我的寫(xiě)操作捌治!
NSError * err = nil ;
NSError *err = nil;
if (![self.clientSocket connectToHost:@"ip地址" onPort: "端口號(hào)" error:&err]) //異步!
{
NSLog(@"I goofed: %@", err);
}
//此時(shí)套接字未連接纽窟。
//但我還是可以開(kāi)始寫(xiě)它肖油!
//庫(kù)將排隊(duì)我的所有寫(xiě)操作,
//在套接字連接后臂港,它將自動(dòng)開(kāi)始執(zhí)行我的寫(xiě)操作森枪!
[socket writeData: data1 withTimeout: - 1 tag: 1 ];
[socket writeData: data2 withTimeout: - 1 tag: 2 ];
-
排隊(duì)讀
: 我們可以通過(guò)長(zhǎng)度獲取到相應(yīng)長(zhǎng)度的數(shù)據(jù),可以很好解決粘包問(wèn)題
[socket readDataToLength: datalength withTimeout: -1 tag: 0];
Tag參數(shù)了解
CocoaAsyncSocket中的tag參數(shù)
是不通過(guò)套接字發(fā)送的审孽,也不是從套接字讀取的县袱。tag參數(shù)只需通過(guò)各種委托方法回顯給您。它旨在幫助簡(jiǎn)化委托方法中的代碼佑力。
[socket writeData: data1 withTimeout: - 1 tag: 1 ];
[socket writeData: data2 withTimeout: - 1 tag: 2 ];
// 當(dāng)我們發(fā)送數(shù)據(jù)時(shí)候使用 tag 標(biāo)記后式散,發(fā)送后可以在委托方法中根據(jù) tag 看到那條數(shù)據(jù)已經(jīng)發(fā)送出去了。
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag :( long)tag{
if(tag == 1)
NSLog(@"First request sent ");
else if(tag == 2)
NSLog(@"Second request sent ");
}
在讀取時(shí)數(shù)據(jù)時(shí)打颤,tag 也很有幫助:
[socket readDataWithTimeout:-1 tag:0];
// 讀取 tag 與上面方法的 tag 值是一一對(duì)應(yīng)的暴拄。
#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12
- (void)socket:(GCDAsyncSocket *)sender didReadData :( NSData *)data withTag :( long)tag{
if(tag == TAG_WELCOME)
{
// 忽略歡迎信息
}
else if(tag == TAG_CAPABILITIES)
{
[self processCapabilities:data];
}
else if (tag == TAG_MSG)
{
[self processMessage: data];
}
}
Tcp 粘包
1. 什么是tcp粘包?
TCP
是面向連接的编饺,面向流的乖篷,提供高可靠性服務(wù)。收發(fā)兩端(客戶(hù)端和服務(wù)器端)都要有一一成對(duì)的socket透且,因此那伐,發(fā)送端為了將多個(gè)發(fā)往接收端的包,更有效的發(fā)到對(duì)方,使用了優(yōu)化方法(Nagle算法)
罕邀,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)畅形,合并成一個(gè)大的數(shù)據(jù)塊,然后進(jìn)行封包诉探。這樣日熬,接收端,就難于分辨出來(lái)了,就會(huì)出現(xiàn)粘包現(xiàn)象
2. TCP粘包解決方案
目前應(yīng)用最廣泛的是在消息的頭部添加數(shù)據(jù)包長(zhǎng)度肾胯,接收方根據(jù)消息長(zhǎng)度進(jìn)行接收竖席;在一條TCP連接上,數(shù)據(jù)的流式傳輸在接收緩沖區(qū)里是有序的敬肚,其主要的問(wèn)題就是第一個(gè)包的包尾與第二個(gè)包的包頭共存接收緩沖區(qū)毕荐,所以根據(jù)長(zhǎng)度讀取是十分合適的;
2.1 解決發(fā)送方粘包
方案一: 發(fā)送產(chǎn)生是因?yàn)镹agle算法合并小數(shù)據(jù)包艳馒,那么可以禁用掉該算法憎亚;
方案二: TCP提供了強(qiáng)制數(shù)據(jù)立即傳送的操作指令push,當(dāng)填入數(shù)據(jù)后調(diào)用操作指令就可以立即將數(shù)據(jù)發(fā)送弄慰,而不必等待發(fā)送緩沖區(qū)填充自動(dòng)發(fā)送第美;
方案三: 數(shù)據(jù)包中加頭,頭部信息為整個(gè)數(shù)據(jù)的長(zhǎng)度(最廣泛最常用)
陆爽;
// `方案三`發(fā)送方解決粘包的代碼部分:
- (void)sendData:(NSData *)data{
NSMutableData *sendData = [NSMutableData data];
// 獲取數(shù)據(jù)長(zhǎng)度
NSInteger datalength = data.length;
// NSInteger長(zhǎng)度轉(zhuǎn) NSData
NSData *lengthData = [NSData dataWithBytes:&datalength length:sizeof(datalength)];
// 長(zhǎng)度幾個(gè)字節(jié)和服務(wù)器協(xié)商好什往。這里我們用的是4個(gè)字節(jié)存儲(chǔ)長(zhǎng)度信息
NSData *newLengthData = [lengthData subdataWithRange:NSMakeRange(0, 4)];
// 拼接長(zhǎng)度信息
[sendData appendData:newLengthData];
//拼接數(shù)據(jù)
[sendData appendData:data];
// 發(fā)送加了長(zhǎng)度信息的包
[self.clientSocket writeData:[sendData copy] withTimeout:-1 tag:0];
}
2.2解決接收方粘包
- 解析數(shù)據(jù)包頭部信息,根據(jù)長(zhǎng)度來(lái)接收慌闭;
(最廣泛最常用)
/**
數(shù)據(jù)緩沖區(qū)
*/
@property (nonatomic, strong) NSMutableData *dataBuffer;;
// 讀取客戶(hù)端發(fā)送的數(shù)據(jù),通過(guò)包頭長(zhǎng)度進(jìn)行拆包
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// 數(shù)據(jù)存入緩沖區(qū)
[self.dataBuffer appendData:data];
// 如果長(zhǎng)度大于4個(gè)字節(jié)别威,表示有數(shù)據(jù)包。4字節(jié)為包頭驴剔,存儲(chǔ)包內(nèi)數(shù)據(jù)長(zhǎng)度
while (self.dataBuffer.length >= 4) {
NSInteger datalength = 0;
// 獲取包頭兔港,并獲取長(zhǎng)度
[[self.dataBuffer subdataWithRange:NSMakeRange(0, 4)] getBytes:&datalength length:sizeof(datalength)];
// 判斷緩存區(qū)內(nèi)是否有包
if (self.dataBuffer.length >= (datalength+4)) {
// 獲取去掉包頭的數(shù)據(jù)
NSData *realData = [[self.dataBuffer subdataWithRange:NSMakeRange(4, datalength)] copy];
// 解析處理
[self handleData:realData socket:sock];
// 移除已經(jīng)拆過(guò)的包
self.dataBuffer = [NSMutableData dataWithData:[self.dataBuffer subdataWithRange:NSMakeRange(datalength+4, self.dataBuffer.length - (datalength+4))]];
}else{
break;
}
}
[sock readDataWithTimeout:-1 tag:0];
}
- 自定義數(shù)據(jù)格式:在數(shù)據(jù)中放入開(kāi)始、結(jié)束標(biāo)識(shí)仔拟;解析時(shí)根據(jù)格式抓取數(shù)據(jù)衫樊,缺點(diǎn)是數(shù)據(jù)內(nèi)不能含有開(kāi)始或結(jié)束標(biāo)識(shí);
- 短連接傳輸利花,建立一次連接只傳輸一次數(shù)據(jù)就關(guān)閉科侈;(不推薦)
注:以上代碼僅提供粘包的解決思路,具體如何解包以及包頭數(shù)據(jù)結(jié)構(gòu)可以和服務(wù)器進(jìn)行商定