GCDAsyncSocket是第三方庫CocoaAsyncSocket其中的一個(gè)類,用于建立可靠的TCP連接。如果想建立UDP連接,可以用GCDAsyncUDPSocket。
用Cocoapods導(dǎo)入GCDAsyncSocket:(Podfile文件中添加下面這句就可以了)
pod 'CocoaAsyncSocket', '7.4.1'
1、創(chuàng)建Socket、并連接
//_connectStatus為socket的連接狀態(tài)
if (_connectStatus == DDSocketConnectStatusConnected){
return;
}
if (self.socket == nil) {
//創(chuàng)建socket
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
NSError *error = nil;
//設(shè)置socket的鏈接地址,并連接服務(wù)器(host是主機(jī)地址,port是端口號(hào))
[self.socket connectToHost:host onPort:port withTimeout:timeout error:&error];
_connectStatus = DDSocketConnectStatusConnecting;
2、連接成功的回調(diào)
如果建連成功之后,會(huì)收到socket成功的回調(diào),在里面你可以做一些事情,我是做了心跳的處理。
/**
當(dāng)成功連接上,該方法會(huì)立刻返回
@param sender socket套接字
@param host 主機(jī)地址
@param port 端口地址
*/
- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(nonnull NSString *)host port:(uint16_t)port
{
DDLog(@"Socket連接成功 host: %@", host);
_connectStatus = DDSocketConnectStatusConnected;
_faliedCount = 0;
//釋放重連定時(shí)器
[self invalidateReconnect];
//初始化發(fā)送心跳的定時(shí)器
[self resumeHeartBeat];
//通信的首個(gè)數(shù)據(jù)包 - 拆包
[self clientSocketReadData];
//連接成功回調(diào)
[self onnectedSocketSucess:YES];
}
3、連接失敗的回調(diào)
如果連接失敗,就會(huì)回調(diào)下面的方法,一般會(huì)在里面做重連socket的操作,需要涉及到重連的時(shí)間和次數(shù)。
/**
連接失敗,該方法會(huì)立刻返回
@param sock socket套接字
@param err 連接失敗的錯(cuò)誤回調(diào)
*/
- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(nullable NSError *)err
{
//未連接的狀態(tài)下進(jìn)行重連
if (_connectStatus != DDSocketConnectStatusDisconnected || !_socket) {
return;
}
//重連次數(shù)判斷
if (_faliedCount < 0 || _faliedCount >= kBeatLimit) {
[self invalidateReconnect];
[self resetSocketStatus];
return;
}
_faliedCount++;
DDLog(@"重連次數(shù):%ld",(long)_faliedCount);
if (_reconnectTimer == nil) {
_reconnectTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(connectSocket) userInfo:nil repeats:YES];
_reconnectTimer.fireDate = [NSDate distantPast]; //啟動(dòng)reconnectTimer
}
}
4、向服務(wù)端發(fā)送數(shù)據(jù)
這一步是建立在socket已經(jīng)建連的基礎(chǔ)上,socket連接成功后,你需要向服務(wù)端發(fā)送數(shù)據(jù)時(shí),調(diào)用下面方法:
[self.socket writeData:requestData withTimeout:-1 tag:0];
下面是你向服務(wù)端發(fā)送數(shù)據(jù)成功的回調(diào)方法:
/**
消息發(fā)送成功
@param sock socket套接字
@param tag
*/
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
DDLog(@"tcp 消息發(fā)送成功");
}
5、接受服務(wù)端的數(shù)據(jù)
這一步是建立在socket已經(jīng)建連的基礎(chǔ)上,socket連接成功后,服務(wù)器向你發(fā)送數(shù)據(jù)后,會(huì)調(diào)用下面方法:
/**
接收消息的回調(diào)方法
@param sock socket套接字
@param data 接收到的二進(jìn)制數(shù)據(jù)
@param tag 消息任務(wù)的類型, 可以自定義 , 根據(jù)定義可以判斷本次連接的類型
*/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
[self.cachedResponseData appendData:data];
[self handleReceivedData:self.cachedResponseData];
}
6、斷開連接
[self.socket disconnect];
socket開發(fā)中需要注意
在開發(fā)中前端和后端會(huì)約定一個(gè)固定的數(shù)據(jù)(消息)格式,按照這個(gè)格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了 粘包 掉包 的問題,數(shù)據(jù)不完整時(shí)也能獲知數(shù)據(jù)的缺失。
在開發(fā)中前端和后端會(huì)約定一個(gè)固定的數(shù)據(jù)(消息)格式,按照這個(gè)格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了 粘包 掉包 的問題,數(shù)據(jù)不完整時(shí)也能獲知數(shù)據(jù)的缺失。
比如,下面是一個(gè)表格,為規(guī)定好的消息格式:
注:以上的消息格式分成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ù)據(jù)返回,處理粘包 掉包問題:
//包返回狀態(tài)
typedef NS_ENUM(NSInteger, DDSocketReturnDataStatus) {
DDSocketReturnDataStatusSucceed = 1, // 完美返回一個(gè)完整包的數(shù)據(jù)
DDSocketReturnDataStatusNoHead = 2, // 包頭不匹配或者不存在(舍棄操作,不處理)
DDSocketReturnDataStatusHeadNoFrist = 3, // 包頭存在但是不在第一個(gè)索引位
DDSocketReturnDataStatusNoComplete = 4, // 不滿足基礎(chǔ)數(shù)據(jù)長度(需要等待繼續(xù)返回)
DDSocketReturnDataStatusLessProtocolBuffers = 5, // protocolBuffers數(shù)據(jù)與長度小于返回長度 (需要等待繼續(xù)返回)
DDSocketReturnDataStatusMoreProtocolBuffers = 6, // protocolBuffers數(shù)據(jù)與長度大于返回長度
};
/**
處理包以及返回的方法,根據(jù)寶返回?cái)?shù)據(jù)的長度,判斷狀態(tài),做相應(yīng)處理
@param indexData 本次連接 接收到的二進(jìn)制數(shù)據(jù)
*/
- (void)handleReceivedData:(NSData *)indexData
{
NSUInteger tempFirstHeaderIndex = 0;
SInt32 tempFirstDataCount = 0;
DDSocketReturnDataStatus returnDataStatus = [GCDAsynSocketModel validationDataPacket:self.cachedResponseData firstHeaderIndex:&tempFirstHeaderIndex firstDataLength: &tempFirstDataCount];
//記錄上一次包頭位置(粘包時(shí)使用)
self.firstHeaderIndex = tempFirstHeaderIndex;
//記錄上一次數(shù)據(jù)的長度(粘包時(shí)使用)
self.firstDataCount = tempFirstDataCount;
switch (returnDataStatus) {
case DDSocketReturnDataStatusSucceed:
{
[self didJustReceivedWholeDataPackage];
}
break;
case DDSocketReturnDataStatusLessProtocolBuffers:
case DDSocketReturnDataStatusNoComplete:
{
[self clientSocketReadData];
}
break;
case DDSocketReturnDataStatusNoHead:
{
[self clientSocketReadData];
}
break;
case DDSocketReturnDataStatusHeadNoFrist:
{
[self resetPackageHeaderLocation];
}
break;
case DDSocketReturnDataStatusMoreProtocolBuffers:
{
[self extractWholeDataPackage];
}
break;
}
}