在上一篇文章文章中,簡單介紹了GCDAsyncSocket的使用,socket建立、鏈接、發送消息、接收消息、關閉socket、粘包分包、以及心跳包機制。而且立下了一個flag,因此在這篇文章,將帶來GCDAsyncSocket的源碼分析,看看在GCDAsyncSocket中是如何運用原生代碼並封裝起來的,在簡單實現的原生代碼基礎上,他又作了什麼樣的操做。數組
咱們仍是按照建立socket、鏈接socket、發送消息、接收消息、關閉socket的順序,一步一步深刻了解GCDAsyncSocket。安全
在開始以前,GCDAsyncSocket.m中聲明瞭許許多多的成員變量,先看看都是啥。bash
@implementation GCDAsyncSocket
{
//flags,當前正在作操做的標識符
uint32_t flags;
uint16_t config;
//代理
__weak id<GCDAsyncSocketDelegate> delegate;
//代理回調的queue
dispatch_queue_t delegateQueue;
//本地IPV4Socket
int socket4FD;
//本地IPV6Socket
int socket6FD;
//unix域的套接字 // 進程通信 locahost VS 127.0.0.1
int socketUN;
//unix域 服務端 url
NSURL *socketUrl;
//狀態Index
int stateIndex;
//本機的IPV4地址 --- 地址host interface
NSData * connectInterface4;
//本機的IPV6地址
NSData * connectInterface6;
//本機unix域地址
NSData * connectInterfaceUN;
//這個類的對Socket的操做都在這個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定時器 重連
dispatch_source_t connectTimer;
dispatch_source_t readSource;
dispatch_source_t writeSource;
dispatch_source_t readTimer;
dispatch_source_t writeTimer;
//讀寫數據包數組 相似queue,最大限制爲5個包 - FIFO
NSMutableArray *readQueue;
NSMutableArray *writeQueue;
//當前正在讀寫數據包
GCDAsyncReadPacket *currentRead;
GCDAsyncWritePacket *currentWrite;
//當前socket未獲取完的數據大小
unsigned long socketFDBytesAvailable;
//全局公用的提早緩衝區
GCDAsyncSocketPreBuffer *preBuffer;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
//讀的數據流 ---- c
CFReadStreamRef readStream;
//寫的數據流
CFWriteStreamRef writeStream;
#endif
//SSL上下文,用來作SSL認證
SSLContextRef sslContext;
//全局公用的SSL的提早緩衝區
GCDAsyncSocketPreBuffer *sslPreBuffer;
size_t sslWriteCachedLength;
//記錄SSL讀取數據錯誤
OSStatus sslErrCode;
//記錄SSL握手的錯誤
OSStatus lastSSLHandshakeError;
//socket隊列的標識key -- key - queue
void *IsOnSocketQueueOrTargetQueueKey;
id userData;
//鏈接備選服務端地址的延時 (另外一個IPV4或IPV6)
NSTimeInterval alternateAddressDelay;
}
複製代碼
建立函數服務器
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
複製代碼
這個init方法最終將會來到,在這個方法裏,socketQueue傳值爲NULL,因此後面若是有sq的部分能夠先行跳過,等梳理完了,再去看看這個sq具體都幹了啥。網絡
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
//這個宏是在sdk6.0以後纔有的,若是是以前的,則OS_OBJECT_USE_OBJC爲0,!0即執行if語句
//對6.0的適配,若是是6.0如下,則去retain release,6.0以後ARC也管理了GCD
//做者很細
#if !OS_OBJECT_USE_OBJC
if (dq) dispatch_retain(dq);
#endif
//建立socket,先都置爲 -1 , 表明socket默認建立失敗
//本機的ipv4
socket4FD = SOCKET_NULL;
//ipv6
socket6FD = SOCKET_NULL;
//應該是UnixSocket
socketUN = SOCKET_NULL;
//url
socketUrl = nil;
//狀態
stateIndex = 0;
//這裏並無sq,能夠選擇跳過
if (sq)
{
//若是scoketQueue是global的,則報錯。斷言必需要一個非並行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
{
//沒有的話建立一個socketQueue, 名字爲:GCDAsyncSocket,NULL = 串行
socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
}
//好比原來爲 0X123 -> NULL 變成 0X222->0X123->NULL
//本身的指針等於本身原來的指針,成二級指針了 看了註釋是爲了之後省略&,讓代碼更可讀?
//這裏不懂做者的用意,繼續往下看
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
//dispatch_queue_set_specific給當前隊里加一個標識 dispatch_get_specific當前線程取出這個標識,判斷是否是在這個隊列
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
//讀的數組
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
//寫的數組
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
//緩衝區 設置大小爲 4kb
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
#pragma mark alternateAddressDelay??
//交替地址延時?? wtf 應該是用來給備用地址的
alternateAddressDelay = 0.3;
}
return self;
}
複製代碼
看完這段代碼...懵逼。只是一些初始化操做。原本還覺得create()
會在這裏面呢,很無奈啊,哎,先無論了,繼續往下看吧。併發
外層調用dom
[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
複製代碼
底層最終會來到這裏,每一個方法都好長啊 - - 。這裏的inInterface傳入的是nil,因此,跟上面那個方法的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) -- 跟蹤當前行爲
LogTrace();
//拿到host ,copy防止值被修改
NSString *host = [inHost copy];
//interface?接口?先無論 反正是nil
NSString *interface = [inInterface copy];
//聲明兩個__block的臨時變量
__block BOOL result = NO;
//error信息
__block NSError *preConnectErr = nil;
//gcdBlock ,都包裹在自動釋放池中 :
// 1: 大量臨時變量 connect : 重連
// 2: 自定義線程管理 : nsoperation
// 3: 非UI 命令 工具
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
// 翻譯:檢查host參數 是否存在問題
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
preConnectErr = [self badParamError:msg];
// 其實就是return,大牛的代碼真是充滿逼格 - ret
// 裏面有註釋,有想法的能夠本身去看看,大概意思就是
// 可讓這個return能更快的被read,後面還有不少地方被調用到
return_from_block;
}
//一個前置的檢查,若是沒經過返回,這q個檢查裏,若是interface有值,則會將本機的IPV4 IPV6的 address設置上。
// 參數 : 指針 操做同一片內存空間
// 由於interface 是nil,因此不會執行return
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
return_from_block;
}
// We've made it past all the checks.咱們已經檢查了全部參數 // It's time to start the connection process.是時候開始鏈接了
//flags 作或等運算。 flags標識爲開始Socket鏈接
flags |= kSocketStarted;
//又是一個{}? 只是爲了標記麼?
LogVerbose(@"Dispatching DNS lookup...");
//極可能給咱們的服務端的參數是一個可變字符串
//因此咱們須要copy,在Block裏同步的執行
//這種基於Block的異步查找,不須要擔憂它被改變
//copy,防止改變
NSString *hostCpy = [host copy];
//拿到狀態 初始化的時候 stateIndex = 0
int aStateIndex = stateIndex;
__weak GCDAsyncSocket *weakSelf = self;
//獲取全局併發Queue
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//異步執行,這裏的autoreleasepool 跟上面的同樣,能夠往上翻
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
//忽視循環引用,牛逼
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//查找錯誤
NSError *lookupErr = nil;
//server地址數組(包含IPV4 IPV6的地址 sockaddr_in六、sockaddr_in類型)
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
//strongSelf
__strong GCDAsyncSocket *strongSelf = weakSelf;
//完整Block安全形態,在加個if
if (strongSelf == nil) return_from_block;
//若是有錯
if (lookupErr)
{
//用cocketQueue
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//一些錯誤處理,清空一些數據等等
[strongSelf lookup:aStateIndex didFail:lookupErr];
}});
}
//正常
else
{
NSData *address4 = nil;
NSData *address6 = nil;
//遍歷地址數組
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;
}
}
//異步去發起
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
// 方法名大概是說,address4 address6 兩個地址都成功獲取到了。
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
}
#pragma clang diagnostic pop
}});
//開啓鏈接超時
[self startConnectTimeout:timeout];
result = YES;
}};
//在socketQueue中執行這個Block
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
//不然同步的調起這個queue去執行
else
dispatch_sync(socketQueue, block);
//若是有錯誤,賦值錯誤
if (errPtr) *errPtr = preConnectErr;
//把鏈接是否成功的result返回
return result;
}
複製代碼
這個connect跟想的也不太同樣,並無熟悉的connect()
,有毒。可是!還知道這個方法裏都幹了啥呢。socket
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
複製代碼
一探究竟!async
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//至少有一個server地址
NSAssert(address4 || address6, @"Expected at least one valid address");
//若是狀態不一致,說明斷開鏈接
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;
//調用鏈接方法,若是失敗,則錯誤返回
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[self closeWithError:err];
}
}
複製代碼
咦,好像有點苗頭,看做者悄咪咪的都幹了些啥。
if (![self connectWithAddress4:address4 address6:address6 error:&err])
複製代碼
繼續點進去看看
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//輸出了兩個地址的信息
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");
// 咦?這不是建立嗎,瞧瞧我發現了啥。
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;
}
//拿到當前狀態
int aStateIndex = stateIndex;
// 我去,這不是鏈接嗎?都悄咪咪的把建立跟鏈接放在這個方法裏了,糟老頭子壞得很。
[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;
}
複製代碼
做者是真的皮啊,把這麼重要的方法,放在一個if裏面?騷仍是你騷啊。 總算是找到建立跟鏈接了,說什麼也要點進去看看吧。 先看建立
//建立Socket
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
// 注意
// 這個connectInterface 建立socketFD4跟6時,分別是傳入了connectInterface4與connectInterface6
// 這兩個值,在preConnectWithInterface時,若是interface不爲空,就會賦值,可是interface一直是nil,因此
// connectInterface4與connectInterface6 都是nil
// 建立socket,用的SOCK_STREAM TCP流
// 總算是看到了熟悉的東西
int socketFD = socket(family, SOCK_STREAM, 0);
//若是建立失敗 SOCKET_NULL = -1
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return socketFD;
}
//和connectInterface綁定,因爲connectInterface 是nil 因此這個方法會放回YES,
//因此不會走進去
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
//綁定失敗,直接關閉返回
[self closeSocket:socketFD];
return SOCKET_NULL;
}
// Prevent SIGPIPE signals
//防止終止進程的信號?
int nosigpipe = 1;
//SO_NOSIGPIPE是爲了不網絡錯誤,而致使進程退出。用這個來避免系統發送signal
//setsockopt()函數,用於任意類型、任意狀態套接口的設置選項值。百度百科有詳解
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
return socketFD;
}
複製代碼
再來就是鏈接socket
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
//已鏈接,關閉鏈接返回
if (self.isConnected)
{
[self closeSocket:socketFD];
return;
}
// Start the connection process in a background queue
//開始鏈接過程,在後臺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"
//調用connect方法,該函數阻塞線程,因此要異步新線程
//客戶端向特定網絡地址的服務器發送鏈接請求,鏈接成功返回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 {
//若是狀態爲已經鏈接,關閉鏈接返回
if (strongSelf.isConnected)
{
[strongSelf closeSocket:socketFD];
// 又是這個裝逼寫法
return_from_block;
}
//說明鏈接成功
if (result == 0)
{
//關閉掉另外一個沒用的socket
[self closeUnusedSocket:socketFD];
//調用didConnect,生成stream,改變狀態等等!
[strongSelf didConnect:aStateIndex];
}
//鏈接失敗
else
{
//關閉當前socket
[strongSelf closeSocket:socketFD];
// If there are no more sockets trying to connect, we inform the error to the delegate
//返回鏈接錯誤的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的建立跟鏈接的實現原理,接下來講讀寫操做。 因爲篇幅問題這裏另起一篇文章看這裏