iOS即時通信之CocoaAsyncSocket源碼解析一

申明:本文內容屬於轉載整理,原文鏈接html

前言:

CocoaAsyncSocket是谷歌的開發者,基於BSD-Socket寫的一個IM框架,它給Mac和iOS提供了易於使用的、強大的異步套接字庫,向上封裝出簡單易用OC接口。省去了咱們面Socket向Socket以及數據流Stream等繁瑣複雜的編程。git

本文爲一個系列,旨在讓你們瞭解CocoaAsyncSocket是如何基於底層進行封裝、工做的。github

注:文中涉及代碼比較多,建議你們結合源碼一塊兒閱讀比較容易能加深理解。這裏有樓主標註好註釋的源碼,有須要的能夠做爲參照:CocoaAsyncSocket源碼註釋編程

若是對該框架用法不熟悉的話,能夠參考樓主以前這篇文章:iOS即時通信,從入門到「放棄」?,或者自行查閱。數組

首先咱們來看看框架的結構圖:

整個庫就這麼兩個類,一個基於TCP,一個基於UDP。其中基於TCPGCDAsyncSocket,大概8000多行代碼。而GCDAsyncUdpSocket稍微少一點,也有5000多行。
因此單純從代碼量上來看,這個庫仍是作了不少事的。緩存

順便提一下,以前這個框架還有一個runloop版的,不過由於功能重疊和其它種種緣由,後續版本便廢棄了,如今僅有GCD版本。安全

本系列咱們將重點來說GCDAsyncSocket這個類。服務器

 1 @implementation GCDAsyncSocket
 2 {
 3     //flags,當前正在作操做的標識符
 4     uint32_t flags;
 5     uint16_t config;
 6     
 7     //代理
 8     __weak id<GCDAsyncSocketDelegate> delegate;
 9     //代理回調的queue
10     dispatch_queue_t delegateQueue;
11     
12     //本地IPV4Socket
13     int socket4FD;
14     //本地IPV6Socket
15     int socket6FD;
16     //unix域的套接字
17     int socketUN;
18     //unix域 服務端 url
19     NSURL *socketUrl;
20     //狀態Index
21     int stateIndex;
22     
23     //本機的IPV4地址
24     NSData * connectInterface4;
25     //本機的IPV6地址
26     NSData * connectInterface6;
27     //本機unix域地址
28     NSData * connectInterfaceUN;
29     
30     //這個類的對Socket的操做都在這個queue中,串行
31     dispatch_queue_t socketQueue;
32     
33     dispatch_source_t accept4Source;
34     dispatch_source_t accept6Source;
35     dispatch_source_t acceptUNSource;
36     
37     //鏈接timer,GCD定時器
38     dispatch_source_t connectTimer;
39     dispatch_source_t readSource;
40     dispatch_source_t writeSource;
41     dispatch_source_t readTimer;
42     dispatch_source_t writeTimer;
43    
44     //讀寫數據包數組 相似queue,最大限制爲5個包
45     NSMutableArray *readQueue;
46     NSMutableArray *writeQueue;
47     
48     //當前正在讀寫數據包
49     GCDAsyncReadPacket *currentRead;
50     GCDAsyncWritePacket *currentWrite;
51     //當前socket未獲取完的數據大小
52     unsigned long socketFDBytesAvailable;
53     
54     //全局公用的提早緩衝區
55     GCDAsyncSocketPreBuffer *preBuffer;
56         
57 #if TARGET_OS_IPHONE
58     CFStreamClientContext streamContext;
59     //讀的數據流
60     CFReadStreamRef readStream;
61     //寫的數據流
62     CFWriteStreamRef writeStream;
63 #endif
64     //SSL上下文,用來作SSL認證
65     SSLContextRef sslContext;
66     
67     //全局公用的SSL的提早緩衝區
68     GCDAsyncSocketPreBuffer *sslPreBuffer;
69     size_t sslWriteCachedLength;
70     
71     //記錄SSL讀取數據錯誤
72     OSStatus sslErrCode;
73     //記錄SSL握手的錯誤
74     OSStatus lastSSLHandshakeError;
75     
76     //socket隊列的標識key
77     void *IsOnSocketQueueOrTargetQueueKey;
78     
79     id userData;
80     
81     //鏈接備選服務端地址的延時 (另外一個IPV4或IPV6)
82     NSTimeInterval alternateAddressDelay;
83 }

這個裏定義了一些屬性,能夠先簡單看看註釋,這裏咱們僅僅先暫時列出來,給你們混個眼熟。
在接下來的代碼中,會大量穿插着這些屬性的使用。因此你們不用以爲困惑,具體做用,咱們後面會一一講清楚的。網絡

接着咱們來看看本文方法一--初始化方法:

  1 //層級調用
  2 - (id)init
  3 {
  4      return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
  5 }
  6 
  7 - (id)initWithSocketQueue:(dispatch_queue_t)sq
  8 {
  9      return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
 10 }
 11 
 12 - (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
 13 {
 14      return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
 15 }
 16 
 17 - (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
 18 {
 19      if((self = [super init]))
 20      {
 21           delegate = aDelegate;
 22           delegateQueue = dq;
 23 
 24          //這個宏是在sdk6.0以後纔有的,若是是以前的,則OS_OBJECT_USE_OBJC爲0,!0即執行if語句
 25         //對6.0的適配,若是是6.0如下,則去retain release,6.0以後ARC也管理了GCD
 26           #if !OS_OBJECT_USE_OBJC
 27 
 28           if (dq) dispatch_retain(dq);
 29           #endif
 30 
 31         //建立socket,先都置爲 -1
 32         //本機的ipv4
 33           socket4FD = SOCKET_NULL;
 34         //ipv6
 35           socket6FD = SOCKET_NULL;
 36         //應該是UnixSocket
 37           socketUN = SOCKET_NULL;
 38         //url
 39           socketUrl = nil;
 40         //狀態
 41           stateIndex = 0;
 42 
 43           if (sq)
 44           {
 45             //若是scoketQueue是global的,則報錯。斷言必需要一個非並行queue。
 46                NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
 47                         @"The given socketQueue parameter must not be a concurrent queue.");
 48                NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
 49                         @"The given socketQueue parameter must not be a concurrent queue.");
 50                NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
 51                         @"The given socketQueue parameter must not be a concurrent queue.");
 52                //拿到scoketQueue
 53                socketQueue = sq;
 54             //iOS6之下retain
 55                #if !OS_OBJECT_USE_OBJC
 56                dispatch_retain(sq);
 57                #endif
 58           }
 59           else
 60           {
 61             //沒有的話建立一個,  名字爲:GCDAsyncSocket,串行
 62                socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
 63           }
 64 
 65           // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
 66           // From the documentation:
 67           //
 68           // > Keys are only compared as pointers and are never dereferenced.
 69           // > Thus, you can use a pointer to a static variable for a specific subsystem or
 70           // > any other value that allows you to identify the value uniquely.
 71           //
 72           // We're just going to use the memory address of an ivar.
 73           // Specifically an ivar that is explicitly named for our purpose to make the code more readable.
 74           //
 75           // However, it feels tedious (and less readable) to include the "&" all the time:
 76           // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
 77           //
 78           // So we're going to make it so it doesn't matter if we use the '&' or not,
 79           // by assigning the value of the ivar to the address of the ivar.
 80           // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
 81 
 82 
 83         //好比原來爲   0X123 -> NULL 變成  0X222->0X123->NULL
 84         //本身的指針等於本身原來的指針,成二級指針了  看了註釋是爲了之後省略&,讓代碼更可讀?
 85           IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
 86 
 87 
 88           void *nonNullUnusedPointer = (__bridge void *)self;
 89 
 90         //dispatch_queue_set_specific給當前隊里加一個標識 dispatch_get_specific當前線程取出這個標識,判斷是否是在這個隊列
 91         //這個key的值其實就是一個一級指針的地址  ,第三個參數把本身傳過去了,上下文對象?第4個參數,爲銷燬的時候用的,能夠指定一個函數
 92           dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
 93           //讀的數組 限制爲5
 94           readQueue = [[NSMutableArray alloc] initWithCapacity:5];
 95           currentRead = nil;
 96 
 97         //寫的數組,限制5
 98           writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
 99           currentWrite = nil;
100 
101         //設置大小爲 4kb
102           preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
103 
104 #pragma mark alternateAddressDelay??
105         //交替地址延時?? wtf
106         alternateAddressDelay = 0.3;
107      }
108      return self;
109 }

詳細的細節能夠看看註釋,這裏初始化了一些屬性:併發

1.代理、以及代理queue的賦值。

2.本機socket的初始化:包括下面3種

//本機的ipv4
socket4FD = SOCKET_NULL;
//ipv6
socket6FD = SOCKET_NULL;
//UnixSocket
socketUN = SOCKET_NULL;

其中值得一提的是第三種:UnixSocket,這個是用於Unix Domin Socket通訊用的。
那麼什麼是Unix Domin Socket呢?
原來它是在socket的框架上發展出一種IPC(進程間通訊)機制,雖然網絡socket也可用於同一臺主機的進程間通信(經過loopback地址127.0.0.1),可是UNIX Domain Socket用於IPC 更有效率 :

  • 不須要通過網絡協議棧
  • 不須要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另外一個進程。這是由於,IPC機制本質上是可靠的通信,而網絡協議是爲不可靠的通信設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,相似於TCP和UDP,可是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

基本上它是當今應用於IPC最主流的方式。至於它到底和普通的socket通訊實現起來有什麼區別,彆着急,咱們接着往下看。

3.生成了一個socketQueue,這個queue是串行的,接下來咱們看代碼就會知道它貫穿於這個類的全部地方。全部對socket以及一些內部數據的相關操做,都須要在這個串行queue中進行。這樣使得整個類沒有加一個鎖,就保證了整個類的線程安全。

4.建立了兩個讀寫隊列(本質數組),接下來咱們全部的讀寫任務,都會先追加在這個隊列最後,而後每次取出隊列中最前面的任務,進行處理。

5.建立了一個全局的數據緩衝區:preBuffer,咱們所操做的數據,大部分都是要先存入這個preBuffer中,而後再從preBuffer取出進行處理的。

6.初始化了一個交替延時變量:alternateAddressDelay,這個變量先簡單的理解下:就是進行另外一個服務端地址請求的延時。後面咱們一講到,你們就明白了。

初始化方法就到此爲止了。 

接着咱們有socket了,咱們若是是客戶端,就須要去connet服務器。

又或者咱們是服務端的話,就須要去bind端口,而且accept,等待客戶端的鏈接。(基本上也沒有用iOS來作服務端的吧...)

這裏咱們先做爲客戶端來看看connect: 

其中和connect相關的方法就這麼多,咱們通常這麼來鏈接到服務端:

[socket connectToHost:Khost onPort:Kport error:nil];

也就是咱們在截圖中選中的方法,那咱們就從這個方法做爲起點,開始講起吧。

本文方法二--connect總方法

  1 /逐級調用
  2 - (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
  3 {
  4     return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
  5 }
  6 
  7 - (BOOL)connectToHost:(NSString *)host
  8                onPort:(uint16_t)port
  9           withTimeout:(NSTimeInterval)timeout
 10                 error:(NSError **)errPtr
 11 {
 12     return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
 13 }
 14 
 15 //多一個inInterface,本機地址
 16 - (BOOL)connectToHost:(NSString *)inHost
 17                onPort:(uint16_t)port
 18          viaInterface:(NSString *)inInterface
 19           withTimeout:(NSTimeInterval)timeout
 20                 error:(NSError **)errPtr
 21 {
 22     //{} 跟蹤當前行爲
 23     LogTrace();
 24 
 25     // Just in case immutable objects were passed
 26     //拿到host ,copy防止值被修改
 27     NSString *host = [inHost copy];
 28     //interface?接口?
 29     NSString *interface = [inInterface copy];
 30 
 31     //聲明兩個__block的
 32     __block BOOL result = NO;
 33     //error信息
 34     __block NSError *preConnectErr = nil;
 35 
 36     //gcdBlock ,都包裹在自動釋放池中
 37     dispatch_block_t block = ^{ @autoreleasepool {
 38 
 39         // Check for problems with host parameter
 40 
 41         if ([host length] == 0)
 42         {
 43             NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
 44             preConnectErr = [self badParamError:msg];
 45 
 46             //其實就是return,大牛的代碼真是充滿逼格
 47             return_from_block;
 48         }
 49 
 50         // Run through standard pre-connect checks
 51         //一個前置的檢查,若是沒經過返回,這個檢查裏,若是interface有值,則會將本機的IPV4 IPV6的 address設置上。
 52         if (![self preConnectWithInterface:interface error:&preConnectErr])
 53         {
 54             return_from_block;
 55         }
 56 
 57         // We've made it past all the checks.
 58         // It's time to start the connection process.
 59         //flags 作或等運算。 flags標識爲開始Socket鏈接
 60         flags |= kSocketStarted;
 61 
 62         //又是一個{}? 只是爲了標記麼?
 63         LogVerbose(@"Dispatching DNS lookup...");
 64 
 65         // It's possible that the given host parameter is actually a NSMutableString.
 66         //極可能給咱們的服務端的參數是一個可變字符串
 67         // So we want to copy it now, within this block that will be executed synchronously.
 68         //因此咱們須要copy,在Block裏同步的執行
 69         // This way the asynchronous lookup block below doesn't have to worry about it changing.
 70         //這種基於Block的異步查找,不須要擔憂它被改變
 71 
 72         //copy,防止改變
 73         NSString *hostCpy = [host copy];
 74 
 75         //拿到狀態
 76         int aStateIndex = stateIndex;
 77         __weak GCDAsyncSocket *weakSelf = self;
 78 
 79         //全局Queue
 80         dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 81         //異步執行
 82         dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
 83             //忽視循環引用
 84         #pragma clang diagnostic push
 85         #pragma clang diagnostic warning "-Wimplicit-retain-self"
 86 
 87             //查找錯誤
 88             NSError *lookupErr = nil;
 89             //server地址數組(包含IPV4 IPV6的地址  sockaddr_in六、sockaddr_in類型)
 90             NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
 91 
 92             //strongSelf
 93             __strong GCDAsyncSocket *strongSelf = weakSelf;
 94 
 95             //完整Block安全形態,在加個if
 96             if (strongSelf == nil) return_from_block;
 97 
 98             //若是有錯
 99             if (lookupErr)
100             {
101                 //用cocketQueue
102                 dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
103                     //一些錯誤處理,清空一些數據等等
104                     [strongSelf lookup:aStateIndex didFail:lookupErr];
105                 }});
106             }
107             //正常
108             else
109             {
110 
111                 NSData *address4 = nil;
112                 NSData *address6 = nil;
113                 //遍歷地址數組
114                 for (NSData *address in addresses)
115                 {
116                     //判斷address4不爲空,且address爲IPV4
117                     if (!address4 && [[self class] isIPv4Address:address])
118                     {
119                         address4 = address;
120                     }
121                     //判斷address6不爲空,且address爲IPV6
122                     else if (!address6 && [[self class] isIPv6Address:address])
123                     {
124                         address6 = address;
125                     }
126                 }
127                 //異步去發起鏈接
128                 dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
129 
130                     [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
131                 }});
132             }
133 
134         #pragma clang diagnostic pop
135         }});
136 
137 
138         //開啓鏈接超時
139         [self startConnectTimeout:timeout];
140 
141         result = YES;
142     }};
143     //在socketQueue中執行這個Block
144     if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
145         block();
146     //不然同步的調起這個queue去執行
147     else
148         dispatch_sync(socketQueue, block);
149 
150     //若是有錯誤,賦值錯誤
151     if (errPtr) *errPtr = preConnectErr;
152     //把鏈接是否成功的result返回
153     return result;
154 }

這個方法很是長,它主要作了如下幾件事:

  • 首先咱們須要說一下的是,整個類大量的會出現LogTrace()相似這樣的宏,咱們點進去發現它的本質只是一個{},什麼事都沒作。

    原來這些宏是爲了追蹤當前執行的流程用的,它被定義在一個大的#if #else中:

#ifndef GCDAsyncSocketLoggingEnabled
#define GCDAsyncSocketLoggingEnabled 0
#endif
#if GCDAsyncSocketLoggingEnabled
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
// https://github.com/robbiehanson/CocoaLumberjack
// 
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
#define LogAsync   YES
#define LogContext GCDAsyncSocketLoggingContext
#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...)    LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogError(frmt, ...)     LogObjc(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...)      LogObjc(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...)      LogObjc(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...)   LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCError(frmt, ...)    LogC(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...)     LogC(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...)     LogC(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...)  LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogTrace()              LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace()             LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
#ifndef GCDAsyncSocketLogLevel
#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
#endif
// Log levels : off, error, warn, info, verbose
static const int logLevel = GCDAsyncSocketLogLevel;
#else
// Logging Disabled
#define LogError(frmt, ...)     {}
#define LogWarn(frmt, ...)      {}
#define LogInfo(frmt, ...)      {}
#define LogVerbose(frmt, ...)   {}
#define LogCError(frmt, ...)    {}
#define LogCWarn(frmt, ...)     {}
#define LogCInfo(frmt, ...)     {}
#define LogCVerbose(frmt, ...)  {}
#define LogTrace()              {}
#define LogCTrace(frmt, ...)    {}
#endif

而此時由於GCDAsyncSocketLoggingEnabled默認爲0,因此僅僅是一個{}。當標記爲1時,這些宏就能夠用來輸出咱們當前的業務流程,極大的方便了咱們的調試過程。

  •  接着咱們回到正題上,咱們定義了一個Block,全部的鏈接操做都被包裹在這個Block中。咱們作了以下判斷:
  //在socketQueue中執行這個Block
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
      block();
  //不然同步的調起這個queue去執行
  else
      dispatch_sync(socketQueue, block);

保證這個鏈接操做必定是在咱們的socketQueue中,並且仍是以串行同步的形式去執行,規避了線程安全的問題。

  • 接着把Block中鏈接過程產生的錯誤進行賦值,而且把鏈接的結果返回出去

//若是有錯誤,賦值錯誤
  if (errPtr) *errPtr = preConnectErr;
  //把鏈接是否成功的result返回
  return result;

接着來看這個方法聲明的Block內部,也就是進行鏈接的真正主題操做,這個鏈接過程將會調用許多函數,一環扣一環,我會盡量用最清晰、詳盡的語言來描述...

 1.這個Block首先作了一些錯誤的判斷,並調用了一些錯誤生成的方法。相似:

 1 if ([host length] == 0)
 2 {
 3      NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
 4      preConnectErr = [self badParamError:msg];
 5 
 6   //其實就是return,大牛的代碼真是充滿逼格
 7      return_from_block;
 8 }
 9 //用該字符串生成一個錯誤,錯誤的域名,錯誤的參數
10 - (NSError *)badParamError:(NSString *)errMsg
11 {
12     NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
13 
14     return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
15 }

2.接着作了一個前置的錯誤檢查:

if (![self preConnectWithInterface:interface error:&preConnectErr])
{
     return_from_block;
}

這個檢查方法,若是沒經過返回NO。而且若是interface有值,則會將本機的IPV4 IPV6address設置上。即咱們以前提到的這兩個屬性:

//本機的IPV4地址
NSData * connectInterface4;
//本機的IPV6地址
NSData * connectInterface6;

咱們來看看這個前置檢查方法:

本文方法三--前置檢查方法

  1 //在鏈接以前的接口檢查,通常咱們傳nil  interface本機的IP 端口等等
  2 - (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
  3 {
  4     //先斷言,若是當前的queue不是初始化quueue,直接報錯
  5     NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
  6 
  7     //無代理
  8     if (delegate == nil) // Must have delegate set
  9     {
 10         if (errPtr)
 11         {
 12             NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
 13             *errPtr = [self badConfigError:msg];
 14         }
 15         return NO;
 16     }
 17     //沒有代理queue
 18     if (delegateQueue == NULL) // Must have delegate queue set
 19     {
 20         if (errPtr)
 21         {
 22             NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
 23             *errPtr = [self badConfigError:msg];
 24         }
 25         return NO;
 26     }
 27 
 28     //當前不是非鏈接狀態
 29     if (![self isDisconnected]) // Must be disconnected
 30     {
 31         if (errPtr)
 32         {
 33             NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
 34             *errPtr = [self badConfigError:msg];
 35         }
 36         return NO;
 37     }
 38 
 39     //判斷是否支持IPV4 IPV6  &位與運算,由於枚舉是用  左位移<<運算定義的,因此能夠用來判斷 config包不包含某個枚舉。由於一個值可能包含好幾個枚舉值,因此這時候不能用==來判斷,只能用&來判斷
 40     BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
 41     BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
 42 
 43     //是否都不支持
 44     if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
 45     {
 46         if (errPtr)
 47         {
 48             NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
 49             *errPtr = [self badConfigError:msg];
 50         }
 51         return NO;
 52     }
 53 
 54     //若是有interface,本機地址
 55     if (interface)
 56     {
 57         NSMutableData *interface4 = nil;
 58         NSMutableData *interface6 = nil;
 59 
 60         //獲得本機的IPV4 IPV6地址
 61         [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
 62 
 63         //若是二者都爲nil
 64         if ((interface4 == nil) && (interface6 == nil))
 65         {
 66             if (errPtr)
 67             {
 68                 NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
 69                 *errPtr = [self badParamError:msg];
 70             }
 71             return NO;
 72         }
 73 
 74         if (isIPv4Disabled && (interface6 == nil))
 75         {
 76             if (errPtr)
 77             {
 78                 NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
 79                 *errPtr = [self badParamError:msg];
 80             }
 81             return NO;
 82         }
 83 
 84         if (isIPv6Disabled && (interface4 == nil))
 85         {
 86             if (errPtr)
 87             {
 88                 NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
 89                 *errPtr = [self badParamError:msg];
 90             }
 91             return NO;
 92         }
 93         //若是都沒問題,則賦值
 94         connectInterface4 = interface4;
 95         connectInterface6 = interface6;
 96     }
 97 
 98     // Clear queues (spurious read/write requests post disconnect)
 99     //清除queue(假的讀寫請求 ,提交斷開鏈接)
100     //讀寫Queue清除
101     [readQueue removeAllObjects];
102     [writeQueue removeAllObjects];
103 
104     return YES;
105 }

又是很是長的一個方法,可是這個方法仍是很是好讀的。

  • 主要是對鏈接前的一個屬性參數的判斷,若是不齊全的話,則填充錯誤指針,而且返回NO。
  • 在這裏若是咱們interface這個參數不爲空話,咱們會額外多執行一些操做。
    首先來說講這個參數是什麼,簡單來說,這個就是咱們設置的本機IP+端口號。照理來講咱們是不須要去設置這個參數的,默認的爲localhost(127.0.0.1)本機地址。而端口號會在本機中取一個空閒可用的端口。
    而咱們一旦設置了這個參數,就會強制本地IP和端口爲咱們指定的。其實這樣設置反而很差,其實你們也能想明白,這裏端口號若是咱們寫死,萬一被其餘進程給佔用了。那麼確定是沒法鏈接成功的。
    因此就有了咱們作IM的時候,通常是不會去指定客戶端bind某一個端口。而是用系統自動去選擇。

  • 咱們最後清空了當前讀寫queue中,全部的任務。

至於有interface,咱們所作的額外操做是什麼呢,咱們接下來看看這個方法:

本文方法四--本地地址綁定方法

  1 - (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
  2                     address6:(NSMutableData **)interfaceAddr6Ptr
  3              fromDescription:(NSString *)interfaceDescription
  4                         port:(uint16_t)port
  5 {
  6     NSMutableData *addr4 = nil;
  7     NSMutableData *addr6 = nil;
  8 
  9     NSString *interface = nil;
 10 
 11     //先用:分割
 12     NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
 13     if ([components count] > 0)
 14     {
 15         NSString *temp = [components objectAtIndex:0];
 16         if ([temp length] > 0)
 17         {
 18             interface = temp;
 19         }
 20     }
 21     if ([components count] > 1 && port == 0)
 22     {
 23         //拿到port strtol函數,將一個字符串,根據base參數轉成長整型,如base值爲10則採用10進制,若base值爲16則採用16進制
 24         long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
 25         //UINT16_MAX,65535最大端口號
 26         if (portL > 0 && portL <= UINT16_MAX)
 27         {
 28             port = (uint16_t)portL;
 29         }
 30     }
 31 
 32     //爲空則本身建立一個 0x00000000 ,全是0 ,爲線路地址
 33     //若是端口爲0 一般用於分析操做系統。這一方法可以工做是由於在一些系統中「0」是無效端口,當你試圖使用一般的閉合端口鏈接它時將產生不一樣的結果。一種典型的掃描,使用IP地址爲0.0.0.0,設置ACK位並在以太網層廣播。
 34     if (interface == nil)
 35     {
 36 
 37         struct sockaddr_in sockaddr4;
 38 
 39         //memset做用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操做的一種最快方法
 40 
 41         //memset(void *s,int ch,size_t n);函數,第一個參數爲指針地址,第二個爲設置值,第三個爲連續設置的長度(大小)
 42         memset(&sockaddr4, 0, sizeof(sockaddr4));
 43         //結構體長度
 44         sockaddr4.sin_len         = sizeof(sockaddr4);
 45         //addressFamily IPv4(AF_INET) 或 IPv6(AF_INET6)。
 46         sockaddr4.sin_family      = AF_INET;
 47         //端口號 htons將主機字節順序轉換成網絡字節順序  16位
 48         sockaddr4.sin_port        = htons(port);
 49         //htonl ,將INADDR_ANY:0.0.0.0,不肯定地址,或者任意地址  htonl 32位。 也是轉爲網絡字節序
 50 
 51         //ipv4 32位  4個字節    INADDR_ANY,0x00000000 (16進制,一個0表明4位,8個0就是32位) =   4個字節的
 52         sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
 53         struct sockaddr_in6 sockaddr6;
 54         memset(&sockaddr6, 0, sizeof(sockaddr6));
 55 
 56         sockaddr6.sin6_len       = sizeof(sockaddr6);
 57         //ipv6
 58         sockaddr6.sin6_family    = AF_INET6;
 59         //port
 60         sockaddr6.sin6_port      = htons(port);
 61 
 62         //共128位
 63         sockaddr6.sin6_addr      = in6addr_any;
 64 
 65         //把這兩個結構體轉成data
 66         addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
 67         addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
 68     }
 69     //若是localhost、loopback 迴環地址,虛擬地址,路由器工做它就存在。通常用來標識路由器
 70     //這兩種的話就賦值爲127.0.0.1,端口爲port
 71     else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
 72     {
 73         // LOOPBACK address
 74 
 75         //ipv4
 76         struct sockaddr_in sockaddr4;
 77         memset(&sockaddr4, 0, sizeof(sockaddr4));
 78 
 79         sockaddr4.sin_len         = sizeof(sockaddr4);
 80         sockaddr4.sin_family      = AF_INET;
 81         sockaddr4.sin_port        = htons(port);
 82 
 83         //#define    INADDR_LOOPBACK        (u_int32_t)0x7f000001
 84         //7f000001->1111111 00000000 00000000 00000001->127.0.0.1
 85         sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 86 
 87         //ipv6
 88         struct sockaddr_in6 sockaddr6;
 89         memset(&sockaddr6, 0, sizeof(sockaddr6));
 90 
 91         sockaddr6.sin6_len       = sizeof(sockaddr6);
 92         sockaddr6.sin6_family    = AF_INET6;
 93         sockaddr6.sin6_port      = htons(port);
 94 
 95         sockaddr6.sin6_addr      = in6addr_loopback;
 96         //賦值
 97         addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
 98         addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
 99     }
100     //非localhost、loopback,去獲取本機IP,看和傳進來Interface是同名或者同IP,相同纔給賦端口號,把數據封裝進Data。不然爲nil
101     else
102     {
103         //轉成cString
104         const char *iface = [interface UTF8String];
105 
106         //定義結構體指針,這個指針是本地IP
107         struct ifaddrs *addrs;
108         const struct ifaddrs *cursor;
109 
110         //獲取到本機IP,爲0說明成功了
111         if ((getifaddrs(&addrs) == 0))
112         {
113             //賦值
114             cursor = addrs;
115             //若是IP不爲空,則循環鏈表去設置
116             while (cursor != NULL)
117             {
118                 //若是 addr4 IPV4地址爲空,並且地址類型爲IPV4
119                 if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
120                 {
121                     // IPv4
122 
123                     struct sockaddr_in nativeAddr4;
124                     //memcpy內存copy函數,把src開始到size的字節數copy到 dest中
125                     memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
126 
127                     //比較兩個字符串是否相同,本機的IP名,和接口interface是否相同
128                     if (strcmp(cursor->ifa_name, iface) == 0)
129                     {
130                         // Name match
131                         //相同則賦值 port
132                         nativeAddr4.sin_port = htons(port);
133                         //用data封號IPV4地址
134                         addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
135                     }
136                     //本機IP名和interface不相同
137                     else
138                     {
139                         //聲明一個IP 16位的數組
140                         char ip[INET_ADDRSTRLEN];
141 
142                         //這裏是轉成了10進制。。(由於獲取到的是二進制IP)
143                         const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
144 
145                         //若是conversion不爲空,說明轉換成功並且 ,比較轉換後的IP,和interface是否相同
146                         if ((conversion != NULL) && (strcmp(ip, iface) == 0))
147                         {
148                             // IP match
149                             //相同則賦值 port
150                             nativeAddr4.sin_port = htons(port);
151 
152                             addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
153                         }
154                     }
155                 }
156                 //IPV6 同樣
157                 else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
158                 {
159                     // IPv6
160 
161                     struct sockaddr_in6 nativeAddr6;
162                     memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
163 
164                     if (strcmp(cursor->ifa_name, iface) == 0)
165                     {
166                         // Name match
167 
168                         nativeAddr6.sin6_port = htons(port);
169 
170                         addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
171                     }
172                     else
173                     {
174                         char ip[INET6_ADDRSTRLEN];
175 
176                         const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
177 
178                         if ((conversion != NULL) && (strcmp(ip, iface) == 0))
179                         {
180                             // IP match
181 
182                             nativeAddr6.sin6_port = htons(port);
183 
184                             addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
185                         }
186                     }
187                 }
188 
189                 //指向鏈表下一個addr
190                 cursor = cursor->ifa_next;
191             }
192             //和getifaddrs對應,釋放這部份內存
193             freeifaddrs(addrs);
194         }
195     }
196     //若是這兩個二級指針存在,則取成一級指針,把addr4賦值給它
197     if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
198     if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
199 

這個方法中,主要是大量的socket相關的函數的調用,會顯得比較難讀一點,其實簡單來說就作了這麼一件事:
interface變成進行socket操做所須要的地址結構體,而後把地址結構體包裹在NSMuttableData中。

這裏,爲了讓你們能更容易理解,我把這個方法涉及到的socket相關函數以及宏(按照調用順序)都列出來:

//拿到port strtol函數,將一個字符串,根據base參數轉成長整型,
//如base值爲10則採用10進制,若base值爲16則採用16進制
long  strtol(const char *__str, char **__endptr, int __base);

//做用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操做的一種最快方法
//第一個參數爲指針地址,第二個爲設置值,第三個爲連續設置的長度(大小)
memset(void *s,int ch,size_t n);

//最大端口號
#define UINT16_MAX        65535

//做用是把主機字節序轉化爲網絡字節序 
htons() //參數16位
htonl() //參數32位
//獲取佔用內存大小
sizeof()
//比較兩個指針,是否相同 相同返回0
int   strcmp(const char *__s1, const char *__s2)


//內存copu函數,把src開始到len的字節數copy到 dest中
memcpy(dest, src, len)   

//inet_pton和inet_ntop這2個IP地址轉換函數,能夠在將IP地址在「點分十進制」和「二進制整數」之間轉換
//參數socklen_t cnt,他是所指向緩存區dst的大小,避免溢出,若是緩存區過小沒法存儲地址的值,則返回一個空指針,並將errno置爲ENOSPC
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);

//獲得本機地址
extern int getifaddrs(struct ifaddrs **);
//釋放本機地址
extern void freeifaddrs(struct ifaddrs *);

還有一些用到的做爲參數的結構體:

//socket通訊用的 IPV4地址結構體 
struct sockaddr_in { 
     __uint8_t sin_len;      //整個結構體大小
     sa_family_t    sin_family;      //協議族,IPV4?IPV6
     in_port_t sin_port;      //端口
     struct    in_addr sin_addr;      //IP地址
     char      sin_zero[8];     //空的佔位符,爲了和其餘地址結構體保持一致大小,方便轉化
};
//IPV6地址結構體,和上面的相似
struct sockaddr_in6 {
     __uint8_t sin6_len; /* length of this struct(sa_family_t) */
     sa_family_t    sin6_family;   /* AF_INET6 (sa_family_t) */
     in_port_t sin6_port;     /* Transport layer port # (in_port_t) */
     __uint32_t     sin6_flowinfo; /* IP6 flow information */
     struct in6_addr     sin6_addr;     /* IP6 address */
     __uint32_t     sin6_scope_id; /* scope zone index */
};

//用來獲取本機IP的參數結構體
struct ifaddrs {
    //指向鏈表的下一個成員
    struct ifaddrs  *ifa_next;
    //接口名稱
    char       *ifa_name;
    //接口標識位(好比當IFF_BROADCAST或IFF_POINTOPOINT設置到此標識位時,影響聯合體變量ifu_broadaddr存儲廣播地址或ifu_dstaddr記錄點對點地址)
    unsigned int     ifa_flags;
    //接口地址
    struct sockaddr *ifa_addr;
    //存儲該接口的子網掩碼;
    struct sockaddr *ifa_netmask;

    //點對點的地址
    struct sockaddr *ifa_dstaddr;
    //ifa_data存儲了該接口協議族的特殊信息,它一般是NULL(通常不關注他)。
    void       *ifa_data;
};

 這一段內容算是比較枯澀了,可是也是瞭解socket編程必經之路。

這裏提到了網絡字節序和主機字節序。咱們建立socket以前,必須把port和host這些參數轉化爲網絡字節序。那麼爲何要這麼作呢?

不一樣的CPU有不一樣的字節序類型 這些字節序是指整數在內存中保存的順序 這個叫作主機序
最多見的有兩種
1. Little endian:將低序字節存儲在起始地址
2. Big endian:將高序字節存儲在起始地址

這樣若是咱們到網絡中,就沒法得知互相的字節序是什麼了,因此咱們就必須統一一套排序,這樣網絡字節序就有它存在的必要了。

網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操做系統等無關。從而能夠保證數據在不一樣主機之間傳輸時可以被正確解釋。網絡字節順序採用big endian排序方式。

你們感興趣能夠到這篇文章中去看看:網絡字節序與主機字節序

除此以外比較重要的就是這幾個地址結構體了。它定義了咱們當前socket的地址信息。包括IP、Port、長度、協議族等等。固然socket中標識爲地址的結構體不止這3種,等咱們後續代碼來補充。

你們瞭解了咱們上述說的知識點,這個方法也就不難度了。這個方法主要是作了本機IPV4IPV6地址的建立和綁定。固然這裏分了幾種狀況:

一、interface爲空的,咱們做爲客戶端不會出現這種狀況。注意以前咱們是這個參數不爲空纔會調入這個方法的。

而這個通常是用於作服務端監聽用的,這裏的處理是給本機地址綁定0地址(任意地址)。那麼這裏這麼作做用是什麼呢?引用一個應用場景來講明:

若是你的服務器有多個網卡(每一個網卡上有不一樣的IP地址),而你的服務(無論是在udp端口上偵聽,仍是在tcp端口上偵聽),出於某種緣由:多是你的服務器操做系統可能隨
時增減IP地址,也有多是爲了省去肯定服務器上有什麼網絡端口(網卡)的麻煩 —— 能夠要在調用bind()的時候,告訴操做系統:「我須要在 yyyy 端口上偵聽,全部發送到
服務器的這個端口,無論是哪一個網卡/哪一個IP地址接收到的數據,都是我處理的。」這時候,服務器程序則在0.0.0.0這個地址上進行偵聽。

二、若是interface爲localhost或者loopback則把IP設置爲127.0.0.1,這裏localhost咱們你們都知道。那麼什麼是loopback呢?

loopback地址叫作迴環地址,他不是一個物理接口上的地址,他是一個虛擬的一個地址,只要路由器在工做,這個地址就存在.它是路由器的惟一標識。
更詳細的內容能夠看看百科:loopback

   三、若是是一個其餘的地址,咱們會去使用getifaddrs()函數獲得本機地址。而後去對比本機名或者本機IP。有一個能相同,咱們就認爲該地址有效,就進行IPV4和IPV6綁定。不然什麼都不作。

 至此這個本機地址綁定咱們就作完了,咱們前面也說過,通常咱們做爲客戶端,是不須要作這一步的。若是咱們不綁定,系統會本身綁定本機IP,而且選擇一個空閒可用的端口。因此這個方法是iOS用來做爲服務端調用的。

 方法三--前置檢查、方法四--本機地址綁定都說完了,咱們繼續接着以前的方法二往下看:

以前講到第3點了:
3.這裏把flag標記爲kSocketStarted:

flags |= kSocketStarted;

源碼中大量的運用了3個位運算符:分別是或(|)、與(&)、取反(~)、運算符。 運用這個標記的好處也很明顯,能夠很簡單的標記當前的狀態,而且由於flags所指向的枚舉值是用左位移的方式:

enum GCDAsyncSocketFlags
{
    kSocketStarted                 = 1 <<  0,  // If set, socket has been started (accepting/connecting)
    kConnected                     = 1 <<  1,  // If set, the socket is connected
    kForbidReadsWrites             = 1 <<  2,  // If set, no new reads or writes are allowed
    kReadsPaused                   = 1 <<  3,  // If set, reads are paused due to possible timeout
    kWritesPaused                  = 1 <<  4,  // If set, writes are paused due to possible timeout
    kDisconnectAfterReads          = 1 <<  5,  // If set, disconnect after no more reads are queued
    kDisconnectAfterWrites         = 1 <<  6,  // If set, disconnect after no more writes are queued
    kSocketCanAcceptBytes          = 1 <<  7,  // If set, we know socket can accept bytes. If unset, it's unknown.
    kReadSourceSuspended           = 1 <<  8,  // If set, the read source is suspended
    kWriteSourceSuspended          = 1 <<  9,  // If set, the write source is suspended
    kQueuedTLS                     = 1 << 10,  // If set, we've queued an upgrade to TLS
    kStartingReadTLS               = 1 << 11,  // If set, we're waiting for TLS negotiation to complete
    kStartingWriteTLS              = 1 << 12,  // If set, we're waiting for TLS negotiation to complete
    kSocketSecure                  = 1 << 13,  // If set, socket is using secure communication via SSL/TLS
    kSocketHasReadEOF              = 1 << 14,  // If set, we have read EOF from socket
    kReadStreamClosed              = 1 << 15,  // If set, we've read EOF plus prebuffer has been drained
    kDealloc                       = 1 << 16,  // If set, the socket is being deallocated
#if TARGET_OS_IPHONE
    kAddedStreamsToRunLoop         = 1 << 17,  // If set, CFStreams have been added to listener thread
    kUsingCFStreamForTLS           = 1 << 18,  // If set, we're forced to use CFStream instead of SecureTransport
    kSecureSocketHasBytesAvailable = 1 << 19,  // If set, CFReadStream has notified us of bytes available
#endif
};

因此flags能夠經過|的方式複合橫跨多個狀態,而且運算也很是輕量級,好處不少,全部的狀態標記的意義能夠在註釋中清晰的看出,這裏把狀態標記爲socket已經開始鏈接了。

4.而後咱們調用了一個全局queue,異步的調用鏈接,這裏又作了兩件事:

  • 第一步是拿到咱們須要鏈接的服務端server的地址數組:
    //server地址數組(包含IPV4 IPV6的地址  sockaddr_in六、sockaddr_in類型)
    NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
  • 第二步是作一些錯誤判斷,而且把地址信息賦值到address6和address6中去,而後異步調用回socketQueue去用另外一個方法去發起鏈接:

  • //異步去發起鏈接
    dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
    
       [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
    }}); 
  • 在這個方法中咱們能夠看到做者這裏把建立server地址這些費時的邏輯操做放在了異步線程中併發進行。而後獲得數據以後又回到了咱們的socketQueue發起下一步的鏈接。

而後這裏又是兩個很大塊的分支,首先咱們來看看server地址的獲取:

本文方法五--建立服務端server地址數據

//根據host、port
+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
{
     LogTrace();

     NSMutableArray *addresses = nil;
     NSError *error = nil;

    //若是Host是這localhost或者loopback
     if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
     {
          // Use LOOPBACK address
          struct sockaddr_in nativeAddr4;
          nativeAddr4.sin_len         = sizeof(struct sockaddr_in);
          nativeAddr4.sin_family      = AF_INET;
          nativeAddr4.sin_port        = htons(port);
          nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        //佔位置0
          memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));

        //ipv6
          struct sockaddr_in6 nativeAddr6;
          nativeAddr6.sin6_len        = sizeof(struct sockaddr_in6);
          nativeAddr6.sin6_family     = AF_INET6;
          nativeAddr6.sin6_port       = htons(port);
          nativeAddr6.sin6_flowinfo   = 0;
          nativeAddr6.sin6_addr       = in6addr_loopback;
          nativeAddr6.sin6_scope_id   = 0;

          // Wrap the native address structures

          NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
          NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];

        //兩個添加進數組
          addresses = [NSMutableArray arrayWithCapacity:2];
          [addresses addObject:address4];
          [addresses addObject:address6];
     }
     else
     {
        //拿到port String
          NSString *portStr = [NSString stringWithFormat:@"%hu", port];



        //定義三個addrInfo  是一個sockaddr結構的鏈表而不是一個地址清單

          struct addrinfo hints, *res, *res0;

        //初始化爲0
          memset(&hints, 0, sizeof(hints));

        //至關於 AF_UNSPEC ,返回的是適用於指定主機名和服務名且適合任何協議族的地址。
          hints.ai_family   = PF_UNSPEC;
          hints.ai_socktype = SOCK_STREAM;
          hints.ai_protocol = IPPROTO_TCP;


        //根據host port,去獲取地址信息。

          int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);

        //出錯
          if (gai_error)
          {   //獲取到錯誤
               error = [self gaiError:gai_error];
          }
        //正確獲取到addrInfo
          else
          {
            //
               NSUInteger capacity = 0;
            //遍歷 res0
               for (res = res0; res; res = res->ai_next)
               {
                //若是有IPV4 IPV6的,capacity+1
                    if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
                         capacity++;
                    }
               }
               //生成一個地址數組,數組爲capacity大小
               addresses = [NSMutableArray arrayWithCapacity:capacity];

            //再去遍歷,爲何不一次遍歷完,僅僅是爲了限制數組的大小?
               for (res = res0; res; res = res->ai_next)
               {
                //IPV4
                    if (res->ai_family == AF_INET)
                    {
                         // Found IPv4 address.
                         // Wrap the native address structure, and add to results.
                         //加到數組中
                         NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
                         [addresses addObject:address4];
                    }
                    else if (res->ai_family == AF_INET6)
                    {
                         // Fixes connection issues with IPv6
                         // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158

                         // Found IPv6 address.
                         // Wrap the native address structure, and add to results.
                    //強轉
                         struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr;
                    //拿到port
                         in_port_t *portPtr = &sockaddr->sin6_port;
                    //若是Port爲0
                         if ((portPtr != NULL) && (*portPtr == 0)) {
                        //賦值,用傳進來的port
                                 *portPtr = htons(port);
                         }
                    //添加到數組
                         NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
                         [addresses addObject:address6];
                    }
               }
            //對應getaddrinfo 釋放內存
               freeaddrinfo(res0);

            //若是地址裏一個沒有,報錯 EAI_FAIL:名字解析中不可恢復的失敗
               if ([addresses count] == 0)
               {
                    error = [self gaiError:EAI_FAIL];
               }
          }
     }
     //賦值錯誤
     if (errPtr) *errPtr = error;
    //返回地址
     return addresses;
}

這個方法根據host進行了劃分:

  1. 若是hostlocalhost或者loopback,則按照咱們以前綁定本機地址那一套生成地址的方式,去生成IPV4和IPV6的地址,而且用NSData包裹住這個地址結構體,裝在NSMutableArray中。
  2. 不是本機地址,那麼咱們就須要根據host和port去建立地址了,這裏用到的是這麼一個函數:

  3. int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

    這個函數主要的做用是:根據hostname(IP),service(port),去獲取地址信息,而且把地址信息傳遞到result中。
    而hints這個參數能夠是一個空指針,也能夠是一個指向某個addrinfo結構體的指針,若是填了,其實它就是一個配置參數,返回的地址信息會和這個配置參數的內容有關,以下例:

  4. 舉例來講:指定的服務既可支持TCP也可支持UDP,因此調用者能夠把hints結構中的ai_socktype成員設置成SOCK_DGRAM使得返回的僅僅是適用於數據報套接口的信息。

    這裏咱們能夠看到result和hints這兩個參數指針指向的都是一個addrinfo的結構體,這是咱們繼上面以來看到的第4種地址結構體了。它的定義以下:

  5. struct addrinfo {
     int    ai_flags;    /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
     int    ai_family;    /* PF_xxx */
     int    ai_socktype;    /* SOCK_xxx */
     int    ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
     socklen_t ai_addrlen;    /* length of ai_addr */
     char    *ai_canonname;    /* canonical name for hostname */
     struct    sockaddr *ai_addr;    /* binary address */
     struct    addrinfo *ai_next;    /* next structure in linked list */
    };
    1. 咱們能夠看到它其中包括了一個IPV4的結構體地址ai_addr,還有一個指向下一個同類型數據節點的指針ai_next
      其餘參數和以前的地址結構體一些參數做用相似,你們能夠對着註釋很好理解,或者仍有疑惑能夠看看這篇:
      socket編程之addrinfo結構體與getaddrinfo函數
      這裏講講ai_next這個指針,由於咱們是去獲取server端的地址,因此極可能有不止一個地址,好比IPV四、IPV6,又或者咱們以前所說的一個服務器有多個網卡,這時候可能就會有多個地址。這些地址就會用ai_next指針串聯起來,造成一個單鏈表。

      而後咱們拿到這個地址鏈表,去遍歷它,對應取出IPV四、IPV6的地址,封裝成NSData並裝到數組中去。

    2. 若是中間有錯誤,賦值錯誤,返回地址數組,理清楚這幾個結構體與函數,這個方法仍是至關容易讀的,具體的細節能夠看看註釋。

    接着咱們回到本文方法二,就要用這個地址數組去作鏈接了。

    //異步去發起鏈接
    dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
    
         [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
    }});

這裏調用了咱們本文方法六--開始鏈接的方法1

 1 //鏈接的最終方法 1
 2 - (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
 3 {
 4      LogTrace();
 5 
 6      NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
 7     //至少有一個server地址
 8      NSAssert(address4 || address6, @"Expected at least one valid address");
 9 
10     //若是狀態不一致,說明斷開鏈接
11      if (aStateIndex != stateIndex)
12      {
13           LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
14 
15           // The connect operation has been cancelled.
16           // That is, socket was disconnected, or connection has already timed out.
17           return;
18      }
19 
20      // Check for problems
21      //分開判斷。
22      BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
23      BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
24 
25      if (isIPv4Disabled && (address6 == nil))
26      {
27           NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
28 
29           [self closeWithError:[self otherError:msg]];
30           return;
31      }
32 
33      if (isIPv6Disabled && (address4 == nil))
34      {
35           NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
36 
37           [self closeWithError:[self otherError:msg]];
38           return;
39      }
40 
41      // Start the normal connection process
42 
43      NSError *err = nil;
44     //調用鏈接方法,若是失敗,則錯誤返回
45      if (![self connectWithAddress4:address4 address6:address6 error:&err])
46      {
47           [self closeWithError:err];
48      }
49 }

這個方法也比較簡單,基本上就是作了一些錯誤的判斷。好比:

  1. 判斷在不在這個socket隊列。
  2. 判斷傳過來的aStateIndex和屬性stateIndex是否是同一個值。說到這個值,不得不提的是大神用的框架,在容錯處理上,作的真不是通常的嚴謹。從這個stateIndex上就能略見一二。
    這個aStateIndex是咱們以前調用方法,用屬性傳過來的,因此按道理說,是確定同樣的。可是就怕在調用過程當中,這個值發生了改變,這時候整個socket配置也就徹底不同了,有可能咱們已經置空地址、銷燬socket、斷開鏈接等等...等咱們後面再來看這個屬性stateIndex在什麼地方會發生改變。
  3. 判斷config中是須要哪一種配置,它的參數對應了一個枚舉:
  4. enum GCDAsyncSocketConfig
    {
     kIPv4Disabled              = 1 << 0,  // If set, IPv4 is disabled
     kIPv6Disabled              = 1 << 1,  // If set, IPv6 is disabled
     kPreferIPv6                = 1 << 2,  // If set, IPv6 is preferred over IPv4
     kAllowHalfDuplexConnection = 1 << 3,  // If set, the socket will stay open even if the read stream closes
    };

前3個你們很好理解,無非就是用IPV4仍是IPV6。

而第4個官方註釋意思是,咱們即便關閉讀的流,也會保持Socket開啓。至於具體是什麼意思,咱們先不在這裏討論,等後文再說。

這裏調用了咱們本文方法七--開始鏈接的方法2

 1 //鏈接最終方法 2。用兩個Server地址去鏈接,失敗返回NO,並填充error
 2 - (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
 3 {
 4     LogTrace();
 5 
 6     NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
 7 
 8     //輸出一些東西?
 9     LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
10     LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
11 
12     // Determine socket type
13 
14     //判斷是否傾向於IPV6
15     BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
16 
17     // Create and bind the sockets
18 
19     //若是有IPV4地址,建立IPV4 Socket
20     if (address4)
21     {
22         LogVerbose(@"Creating IPv4 socket");
23 
24         socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
25     }
26     //若是有IPV6地址,建立IPV6 Socket
27     if (address6)
28     {
29         LogVerbose(@"Creating IPv6 socket");
30 
31         socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
32     }
33 
34     //若是都爲空,直接返回
35     if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
36     {
37         return NO;
38     }
39 
40     //主選socketFD,備選alternateSocketFD
41     int socketFD, alternateSocketFD;
42     //主選地址和備選地址
43     NSData *address, *alternateAddress;
44 
45     //IPV6
46     if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
47     {
48         socketFD = socket6FD;
49         alternateSocketFD = socket4FD;
50         address = address6;
51         alternateAddress = address4;
52     }
53     //主選IPV4
54     else
55     {
56         socketFD = socket4FD;
57         alternateSocketFD = socket6FD;
58         address = address4;
59         alternateAddress = address6;
60     }
61     //拿到當前狀態
62     int aStateIndex = stateIndex;
63     //用socket和address去鏈接
64     [self connectSocket:socketFD address:address stateIndex:aStateIndex];
65 
66     //若是有備選地址
67     if (alternateAddress)
68     {
69         //延遲去鏈接備選的地址
70         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
71             [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
72         });
73     }
74 
75     return YES;
76 }

這個方法也僅僅是鏈接中過渡的一個方法,作的事也很是簡單:

  1. 就是拿到IPV4和IPV6地址,先去建立對應的socket,注意這個socket是本機客戶端的,和server端沒有關係。這裏服務端的IPV4和IPV6地址僅僅是用來判斷是否須要去建立對應的本機Socket。這裏去建立socket會帶上咱們以前生成的本地地址信息connectInterface4或者connectInterface6
  2. 根據咱們的config配置,獲得主選鏈接和備選鏈接。 而後先去鏈接主選鏈接地址,在用咱們一開始初始化中設置的屬性alternateAddressDelay,就是這個備選鏈接延時的屬性,去延時鏈接備選地址(固然若是主選地址在此時已經鏈接成功,會再次鏈接致使socket錯誤,而且關閉)。

這兩步分別調用了各自的方法去實現,接下來咱們先來看建立本機Socket的方法:

 本文方法八--建立Socket:

 1 //建立Socket
 2 - (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
 3 {
 4     //建立socket,用的SOCK_STREAM TCP流
 5     int socketFD = socket(family, SOCK_STREAM, 0);
 6     //若是建立失敗
 7     if (socketFD == SOCKET_NULL)
 8     {
 9         if (errPtr)
10             *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
11 
12         return socketFD;
13     }
14 
15     //和connectInterface綁定
16     if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
17     {
18         //綁定失敗,直接關閉返回
19         [self closeSocket:socketFD];
20 
21         return SOCKET_NULL;
22     }
23 
24     // Prevent SIGPIPE signals
25     //防止終止進程的信號?
26     int nosigpipe = 1;
27     //SO_NOSIGPIPE是爲了不網絡錯誤,而致使進程退出。用這個來避免系統發送signal
28     setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
29 
30     return socketFD;
31 }

這個方法作了這麼幾件事:

 

一、建立了一個socket:

 

  1. //建立一個socket,返回值爲Int。(注scoket其實就是Int類型)
     //第一個參數addressFamily IPv4(AF_INET) 或 IPv6(AF_INET6)。
     //第二個參數 type 表示 socket 的類型,一般是流stream(SOCK_STREAM) 或數據報文datagram(SOCK_DGRAM)
     //第三個參數 protocol 參數一般設置爲0,以便讓系統自動爲選擇咱們合適的協議,對於 stream socket 來講會是 TCP 協議(IPPROTO_TCP),而對於 datagram來講會是 UDP 協議(IPPROTO_UDP)。
    int socketFD = socket(family, SOCK_STREAM, 0);

 

二、其實這個函數在以前那篇IM文章中也講過了,你們參考參考註釋看看就能夠了,這裏若是返回值爲-1,說明建立失敗。

三、去綁定咱們以前建立的本地地址,它調用了另一個方法來實現。

四、最後咱們調用了以下函數:

 

setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));

 

五、那麼這個函數是作什麼用的呢?簡單來講,它就是給咱們的socket加一些額外的設置項,來配置socket的一些行爲。它還有許多的用法,具體能夠參考這篇文章:setsockopt函數

而這裏的目的是爲了來避免網絡錯誤而出現的進程退出的狀況,調用了這行函數,網絡錯誤後,系統再也不發送進程退出的信號。
關於這個進程退出的錯誤能夠參考這篇文章:Mac OSX下SO_NOSIGPIPE的怪異表現

 

 總結,本文未完,續下節 :iOS即時通信之CocoaAsyncSocket源碼解析二

相關文章
相關標籤/搜索