zoukankan      html  css  js  c++  java
  • iOS即时通讯之CocoaAsyncSocket源码解析一

    申明:本文内容属于转载整理,原文连接

    前言:

    CocoaAsyncSocket是谷歌的开发者,基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的、强大的异步套接字库,向上封装出简单易用OC接口。省去了我们面Socket向Socket以及数据流Stream等繁琐复杂的编程。

    本文为一个系列,旨在让大家了解CocoaAsyncSocket是如何基于底层进行封装、工作的。

    注:文中涉及代码比较多,建议大家结合源码一起阅读比较容易能加深理解。这里有楼主标注好注释的源码,有需要的可以作为参照: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_in6、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地址的创建和绑定。当然这里分了几种情况:

    1、interface为空的,我们作为客户端不会出现这种情况。注意之前我们是这个参数不为空才会调入这个方法的。

    而这个一般是用于做服务端监听用的,这里的处理是给本机地址绑定0地址(任意地址)。那么这里这么做作用是什么呢?引用一个应用场景来说明:

    如果你的服务器有多个网卡(每个网卡上有不同的IP地址),而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:可能是你的服务器操作系统可能随
    时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 —— 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所有发送到
    服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器程序则在0.0.0.0这个地址上进行侦听。

    2、如果interface为localhost或者loopback则把IP设置为127.0.0.1,这里localhost我们大家都知道。那么什么是loopback呢?

    loopback地址叫做回环地址,他不是一个物理接口上的地址,他是一个虚拟的一个地址,只要路由器在工作,这个地址就存在.它是路由器的唯一标识。
    更详细的内容可以看看百科:loopback

       3、如果是一个其他的地址,我们会去使用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_in6、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端的地址,所以很可能有不止一个地址,比如IPV4、IPV6,又或者我们之前所说的一个服务器有多个网卡,这时候可能就会有多个地址。这些地址就会用ai_next指针串联起来,形成一个单链表。

        然后我们拿到这个地址链表,去遍历它,对应取出IPV4、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 }

    这个方法做了这么几件事:

    1、创建了一个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);

    2、其实这个函数在之前那篇IM文章中也讲过了,大家参考参考注释看看就可以了,这里如果返回值为-1,说明创建失败。

    3、去绑定我们之前创建的本地地址,它调用了另外一个方法来实现。

    4、最后我们调用了如下函数:

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

    5、那么这个函数是做什么用的呢?简单来说,它就是给我们的socket加一些额外的设置项,来配置socket的一些行为。它还有许多的用法,具体可以参考这篇文章:setsockopt函数

    而这里的目的是为了来避免网络错误而出现的进程退出的情况,调用了这行函数,网络错误后,系统不再发送进程退出的信号。
    关于这个进程退出的错误可以参考这篇文章:Mac OSX下SO_NOSIGPIPE的怪异表现

     总结,本文未完,续下节 :iOS即时通讯之CocoaAsyncSocket源码解析二

  • 相关阅读:
    linux命令行
    mybatis中#{}和${}的区别
    @InitBinder的作用
    mui 实用封装销毁页面
    【SQLite】简单的基本使用步骤
    常用的一些操作方法
    【HttpWeb】Post和GET请求基本封装
    【接口验证】特性验证参数
    小谈单例模式
    vs下开端口直接调试iis
  • 原文地址:https://www.cnblogs.com/francisblogs/p/6825312.html
Copyright © 2011-2022 走看看