序言
在上一篇文章文章中酬滤,簡單介紹了GCDAsyncSocket的使用签餐,socket創(chuàng)建、連接盯串、發(fā)送消息氯檐、接收消息、關(guān)閉socket体捏、粘包分包冠摄、以及心跳包機(jī)制。并且立下了一個(gè)flag几缭,所以在這篇文章河泳,將帶來GCDAsyncSocket的源碼分析,看看在GCDAsyncSocket中是如何運(yùn)用原生代碼并封裝起來的年栓,在簡單實(shí)現(xiàn)的原生代碼基礎(chǔ)上拆挥,他又做了什么樣的操作。
我們還是按照創(chuàng)建socket某抓、連接socket纸兔、發(fā)送消息黄锤、接收消息、關(guān)閉socket的順序食拜,一步一步深入了解GCDAsyncSocket。
1. GCDAsyncSocket初始化
在開始之前副编,GCDAsyncSocket.m中聲明了許許多多的成員變量负甸,先看看都是啥。
@implementation GCDAsyncSocket
{
//flags痹届,當(dāng)前正在做操作的標(biāo)識(shí)符
uint32_t flags;
uint16_t config;
//代理
__weak id<GCDAsyncSocketDelegate> delegate;
//代理回調(diào)的queue
dispatch_queue_t delegateQueue;
//本地IPV4Socket
int socket4FD;
//本地IPV6Socket
int socket6FD;
//unix域的套接字 // 進(jìn)程通訊 locahost VS 127.0.0.1
int socketUN;
//unix域 服務(wù)端 url
NSURL *socketUrl;
//狀態(tài)Index
int stateIndex;
//本機(jī)的IPV4地址 --- 地址host interface
NSData * connectInterface4;
//本機(jī)的IPV6地址
NSData * connectInterface6;
//本機(jī)unix域地址
NSData * connectInterfaceUN;
//這個(gè)類的對(duì)Socket的操作都在這個(gè)queue中呻待,串行
dispatch_queue_t socketQueue;
// 源 ---> mergdata get_data buffer tls ssl CFStream
// data
dispatch_source_t accept4Source;
dispatch_source_t accept6Source;
dispatch_source_t acceptUNSource;
//連接timer,GCD定時(shí)器 重連
dispatch_source_t connectTimer;
dispatch_source_t readSource;
dispatch_source_t writeSource;
dispatch_source_t readTimer;
dispatch_source_t writeTimer;
//讀寫數(shù)據(jù)包數(shù)組 類似queue,最大限制為5個(gè)包 - FIFO
NSMutableArray *readQueue;
NSMutableArray *writeQueue;
//當(dāng)前正在讀寫數(shù)據(jù)包
GCDAsyncReadPacket *currentRead;
GCDAsyncWritePacket *currentWrite;
//當(dāng)前socket未獲取完的數(shù)據(jù)大小
unsigned long socketFDBytesAvailable;
//全局公用的提前緩沖區(qū)
GCDAsyncSocketPreBuffer *preBuffer;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
//讀的數(shù)據(jù)流 ---- c
CFReadStreamRef readStream;
//寫的數(shù)據(jù)流
CFWriteStreamRef writeStream;
#endif
//SSL上下文队腐,用來做SSL認(rèn)證
SSLContextRef sslContext;
//全局公用的SSL的提前緩沖區(qū)
GCDAsyncSocketPreBuffer *sslPreBuffer;
size_t sslWriteCachedLength;
//記錄SSL讀取數(shù)據(jù)錯(cuò)誤
OSStatus sslErrCode;
//記錄SSL握手的錯(cuò)誤
OSStatus lastSSLHandshakeError;
//socket隊(duì)列的標(biāo)識(shí)key -- key - queue
void *IsOnSocketQueueOrTargetQueueKey;
id userData;
//連接備選服務(wù)端地址的延時(shí) (另一個(gè)IPV4或IPV6)
NSTimeInterval alternateAddressDelay;
}
創(chuàng)建函數(shù)
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
這個(gè)init方法最終將會(huì)來到蚕捉,在這個(gè)方法里,socketQueue傳值為NULL柴淘,所以后面如果有sq的部分可以先行跳過迫淹,等梳理完了,再去看看這個(gè)sq具體都干了啥为严。
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
//這個(gè)宏是在sdk6.0之后才有的,如果是之前的,則OS_OBJECT_USE_OBJC為0敛熬,!0即執(zhí)行if語句
//對(duì)6.0的適配,如果是6.0以下第股,則去retain release应民,6.0之后ARC也管理了GCD
//作者很細(xì)
#if !OS_OBJECT_USE_OBJC
if (dq) dispatch_retain(dq);
#endif
//創(chuàng)建socket,先都置為 -1 , 代表socket默認(rèn)創(chuàng)建失敗
//本機(jī)的ipv4
socket4FD = SOCKET_NULL;
//ipv6
socket6FD = SOCKET_NULL;
//應(yīng)該是UnixSocket
socketUN = SOCKET_NULL;
//url
socketUrl = nil;
//狀態(tài)
stateIndex = 0;
//這里并沒有sq夕吻,可以選擇跳過
if (sq)
{
//如果scoketQueue是global的,則報(bào)錯(cuò)诲锹。斷言必須要一個(gè)非并行queue。
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
//拿到scoketQueue
socketQueue = sq;
//iOS6之下retain
#if !OS_OBJECT_USE_OBJC
dispatch_retain(sq);
#endif
}
else
{
//沒有的話創(chuàng)建一個(gè)socketQueue涉馅, 名字為:GCDAsyncSocket,NULL = 串行
socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
}
//比如原來為 0X123 -> NULL 變成 0X222->0X123->NULL
//自己的指針等于自己原來的指針归园,成二級(jí)指針了 看了注釋是為了以后省略&,讓代碼更可讀?
//這里不懂作者的用意稚矿,繼續(xù)往下看
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
//dispatch_queue_set_specific給當(dāng)前隊(duì)里加一個(gè)標(biāo)識(shí) dispatch_get_specific當(dāng)前線程取出這個(gè)標(biāo)識(shí)蔓倍,判斷是不是在這個(gè)隊(duì)列
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
//讀的數(shù)組
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
//寫的數(shù)組
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
//緩沖區(qū) 設(shè)置大小為 4kb
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
#pragma mark alternateAddressDelay??
//交替地址延時(shí)?盐捷? wtf 應(yīng)該是用來給備用地址的
alternateAddressDelay = 0.3;
}
return self;
}
看完這段代碼...懵逼偶翅。只是一些初始化操作。本來還以為create()
會(huì)在這里面呢碉渡,很無奈啊聚谁,哎,先不管了滞诺,繼續(xù)往下看吧形导。
2. GCDAsyncSocket Connect
外層調(diào)用
[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
底層最終會(huì)來到這里环疼,每個(gè)方法都好長啊 - - 。這里的inInterface傳入的是nil朵耕,所以炫隶,跟上面那個(gè)方法的sq一樣,如果有遇到可以選擇跳過阎曹。
- (BOOL)connectToHost:(NSString *)inHost
onPort:(uint16_t)port
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
//LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -- 跟蹤當(dāng)前行為
LogTrace();
//拿到host ,copy防止值被修改
NSString *host = [inHost copy];
//interface伪阶?接口?先不管 反正是nil
NSString *interface = [inInterface copy];
//聲明兩個(gè)__block的臨時(shí)變量
__block BOOL result = NO;
//error信息
__block NSError *preConnectErr = nil;
//gcdBlock ,都包裹在自動(dòng)釋放池中 :
// 1: 大量臨時(shí)變量 connect : 重連
// 2: 自定義線程管理 : nsoperation
// 3: 非UI 命令 工具
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
// 翻譯:檢查host參數(shù) 是否存在問題
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
preConnectErr = [self badParamError:msg];
// 其實(shí)就是return,大牛的代碼真是充滿逼格 - ret
// 里面有注釋处嫌,有想法的可以自己去看看栅贴,大概意思就是
// 可以讓這個(gè)return能更快的被read,后面還有很多地方被調(diào)用到
return_from_block;
}
//一個(gè)前置的檢查,如果沒通過返回熏迹,這q個(gè)檢查里檐薯,如果interface有值,則會(huì)將本機(jī)的IPV4 IPV6的 address設(shè)置上注暗。
// 參數(shù) : 指針 操作同一片內(nèi)存空間
// 因?yàn)閕nterface 是nil坛缕,所以不會(huì)執(zhí)行return
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
return_from_block;
}
// We've made it past all the checks.我們已經(jīng)檢查了所有參數(shù)
// It's time to start the connection process.是時(shí)候開始連接了
//flags 做或等運(yùn)算。 flags標(biāo)識(shí)為開始Socket連接
flags |= kSocketStarted;
//又是一個(gè){}? 只是為了標(biāo)記么捆昏?
LogVerbose(@"Dispatching DNS lookup...");
//很可能給我們的服務(wù)端的參數(shù)是一個(gè)可變字符串
//所以我們需要copy祷膳,在Block里同步的執(zhí)行
//這種基于Block的異步查找,不需要擔(dān)心它被改變
//copy屡立,防止改變
NSString *hostCpy = [host copy];
//拿到狀態(tài) 初始化的時(shí)候 stateIndex = 0
int aStateIndex = stateIndex;
__weak GCDAsyncSocket *weakSelf = self;
//獲取全局并發(fā)Queue
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//異步執(zhí)行直晨,這里的autoreleasepool 跟上面的一樣,可以往上翻
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
//忽視循環(huán)引用膨俐,牛逼
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//查找錯(cuò)誤
NSError *lookupErr = nil;
//server地址數(shù)組(包含IPV4 IPV6的地址 sockaddr_in6勇皇、sockaddr_in類型)
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
//strongSelf
__strong GCDAsyncSocket *strongSelf = weakSelf;
//完整Block安全形態(tài),在加個(gè)if
if (strongSelf == nil) return_from_block;
//如果有錯(cuò)
if (lookupErr)
{
//用cocketQueue
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//一些錯(cuò)誤處理焚刺,清空一些數(shù)據(jù)等等
[strongSelf lookup:aStateIndex didFail:lookupErr];
}});
}
//正常
else
{
NSData *address4 = nil;
NSData *address6 = nil;
//遍歷地址數(shù)組
for (NSData *address in addresses)
{
//判斷address4為空敛摘,且address為IPV4
if (!address4 && [[self class] isIPv4Address:address])
{
address4 = address;
}
//判斷address6為空,且address為IPV6
else if (!address6 && [[self class] isIPv6Address:address])
{
address6 = address;
}
}
//異步去發(fā)起
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
// 方法名大概是說乳愉,address4 address6 兩個(gè)地址都成功獲取到了兄淫。
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
}
#pragma clang diagnostic pop
}});
//開啟連接超時(shí)
[self startConnectTimeout:timeout];
result = YES;
}};
//在socketQueue中執(zhí)行這個(gè)Block
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
//否則同步的調(diào)起這個(gè)queue去執(zhí)行
else
dispatch_sync(socketQueue, block);
//如果有錯(cuò)誤,賦值錯(cuò)誤
if (errPtr) *errPtr = preConnectErr;
//把連接是否成功的result返回
return result;
}
這個(gè)connect跟想的也不太一樣蔓姚,并沒有熟悉的connect()
捕虽,有毒。但是坡脐!還知道這個(gè)方法里都干了啥呢泄私。
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
一探究竟!
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//至少有一個(gè)server地址
NSAssert(address4 || address6, @"Expected at least one valid address");
//如果狀態(tài)不一致,說明斷開連接
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
// Check for problems
//分開判斷晌端。
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address6 == nil))
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
[self closeWithError:[self otherError:msg]];
return;
}
if (isIPv6Disabled && (address4 == nil))
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
[self closeWithError:[self otherError:msg]];
return;
}
// Start the normal connection process
NSError *err = nil;
//調(diào)用連接方法捅暴,如果失敗,則錯(cuò)誤返回
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[self closeWithError:err];
}
}
咦咧纠,好像有點(diǎn)苗頭蓬痒,看作者悄咪咪的都干了些啥。
if (![self connectWithAddress4:address4 address6:address6 error:&err])
繼續(xù)點(diǎn)進(jìn)去看看
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//輸出了兩個(gè)地址的信息
LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
//判斷是否傾向于IPV6
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
// Create and bind the sockets
//如果有IPV4地址
if (address4)
{
LogVerbose(@"Creating IPv4 socket");
// 咦漆羔?這不是創(chuàng)建嗎梧奢,瞧瞧我發(fā)現(xiàn)了啥。
socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
}
//如果有IPV6地址钧椰,同上
if (address6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
}
//如果都為空,直接返回
if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
{
return NO;
}
//主選socketFD,備選alternateSocketFD
int socketFD, alternateSocketFD;
//主選地址和備選地址
NSData *address, *alternateAddress;
//IPV6
if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
{
socketFD = socket6FD;
alternateSocketFD = socket4FD;
address = address6;
alternateAddress = address4;
}
//主選IPV4
else
{
socketFD = socket4FD;
alternateSocketFD = socket6FD;
address = address4;
alternateAddress = address6;
}
//拿到當(dāng)前狀態(tài)
int aStateIndex = stateIndex;
// 我去符欠,這不是連接嗎嫡霞?都悄咪咪的把創(chuàng)建跟連接放在這個(gè)方法里了,糟老頭子壞得很希柿。
[self connectSocket:socketFD address:address stateIndex:aStateIndex];
//如果有備選地址
if (alternateAddress)
{
//延遲去連接備選的地址
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
[self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
});
}
return YES;
}
作者是真的皮啊诊沪,把這么重要的方法,放在一個(gè)if里面曾撤?騷還是你騷啊端姚。
總算是找到創(chuàng)建跟連接了,說什么也要點(diǎn)進(jìn)去看看吧挤悉。
先看創(chuàng)建
//創(chuàng)建Socket
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
// 注意
// 這個(gè)connectInterface 創(chuàng)建socketFD4跟6時(shí)渐裸,分別是傳入了connectInterface4與connectInterface6
// 這兩個(gè)值,在preConnectWithInterface時(shí)装悲,如果interface不為空昏鹃,就會(huì)賦值,但是interface一直是nil,所以
// connectInterface4與connectInterface6 都是nil
// 創(chuàng)建socket,用的SOCK_STREAM TCP流
// 總算是看到了熟悉的東西
int socketFD = socket(family, SOCK_STREAM, 0);
//如果創(chuàng)建失敗 SOCKET_NULL = -1
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return socketFD;
}
//和connectInterface綁定诀诊,由于connectInterface 是nil 所以這個(gè)方法會(huì)放回YES,
//所以不會(huì)走進(jìn)去
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
//綁定失敗洞渤,直接關(guān)閉返回
[self closeSocket:socketFD];
return SOCKET_NULL;
}
// Prevent SIGPIPE signals
//防止終止進(jìn)程的信號(hào)?
int nosigpipe = 1;
//SO_NOSIGPIPE是為了避免網(wǎng)絡(luò)錯(cuò)誤属瓣,而導(dǎo)致進(jìn)程退出载迄。用這個(gè)來避免系統(tǒng)發(fā)送signal
//setsockopt()函數(shù),用于任意類型抡蛙、任意狀態(tài)套接口的設(shè)置選項(xiàng)值护昧。百度百科有詳解
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
return socketFD;
}
再來就是連接socket
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
//已連接,關(guān)閉連接返回
if (self.isConnected)
{
[self closeSocket:socketFD];
return;
}
// Start the connection process in a background queue
//開始連接過程粗截,在后臺(tái)queue中
__weak GCDAsyncSocket *weakSelf = self;
//獲取到全局Queue
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//新線程
dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//調(diào)用connect方法捏卓,該函數(shù)阻塞線程,所以要異步新線程
//客戶端向特定網(wǎng)絡(luò)地址的服務(wù)器發(fā)送連接請(qǐng)求,連接成功返回0怠晴,失敗返回 -1遥金。
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
//老樣子,安全判斷
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
//在socketQueue中蒜田,開辟線程
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//如果狀態(tài)為已經(jīng)連接稿械,關(guān)閉連接返回
if (strongSelf.isConnected)
{
[strongSelf closeSocket:socketFD];
// 又是這個(gè)裝逼寫法
return_from_block;
}
//說明連接成功
if (result == 0)
{
//關(guān)閉掉另一個(gè)沒用的socket
[self closeUnusedSocket:socketFD];
//調(diào)用didConnect,生成stream冲粤,改變狀態(tài)等等美莫!
[strongSelf didConnect:aStateIndex];
}
//連接失敗
else
{
//關(guān)閉當(dāng)前socket
[strongSelf closeSocket:socketFD];
// If there are no more sockets trying to connect, we inform the error to the delegate
//返回連接錯(cuò)誤的error
if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
{
NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
[strongSelf didNotConnect:aStateIndex error:error];
}
}
}});
#pragma clang diagnostic pop
});
//輸出正在連接中
LogVerbose(@"Connecting...");
}
至此,我們就看到了socket的創(chuàng)建跟連接的實(shí)現(xiàn)原理梯捕,接下來說讀寫操作厢呵。
由于篇幅問題這里另起一篇文章看這里