iOS即時(shí)通訊進(jìn)階 - CocoaAsyncSocket源碼解析(Connect篇)

前言:

CocoaAsyncSocket是谷歌的開發(fā)者,基于BSD-Socket寫的一個(gè)IM框架,它給Mac和iOS提供了易于使用的、強(qiáng)大的異步套接字庫(kù),向上封裝出簡(jiǎn)單易用OC接口。省去了我們面向Socket以及數(shù)據(jù)流Stream等繁瑣復(fù)雜的編程。
本文為一個(gè)系列,旨在讓大家了解CocoaAsyncSocket是如何基于底層進(jìn)行封裝、工作的。

注:文中涉及代碼比較多,建議大家結(jié)合源碼一起閱讀比較容易能加深理解。這里有樓主標(biāo)注好注釋的源碼,有需要的可以作為參照:CocoaAsyncSocket源碼注釋

如果對(duì)該框架用法不熟悉的話,可以參考樓主之前這篇文章:iOS即時(shí)通訊,從入門到“放棄”?,或者自行查閱。

正文:
首先我們來看看框架的結(jié)構(gòu)圖:

整個(gè)庫(kù)就這么兩個(gè)類,一個(gè)基于TCP,一個(gè)基于UDP。其中基于TCP的GCDAsyncSocket,大概8000多行代碼。而GCDAsyncUdpSocket稍微少一點(diǎn),也有5000多行。
所以單純從代碼量上來看,這個(gè)庫(kù)還是做了很多事的。

順便提一下,之前這個(gè)框架還有一個(gè)runloop版的,不過因?yàn)楣δ苤丿B和其它種種原因,后續(xù)版本便廢棄了,現(xiàn)在僅有GCD版本。

本系列我們將重點(diǎn)來講GCDAsyncSocket這個(gè)類。

我們先來看看這個(gè)類的屬性:
@implementation GCDAsyncSocket
{
    //flags,當(dāng)前正在做操作的標(biāo)識(shí)符
    uint32_t flags;
    uint16_t config;
    
    //代理
    __weak id<GCDAsyncSocketDelegate> delegate;
    //代理回調(diào)的queue
    dispatch_queue_t delegateQueue;
    
    //本地IPV4Socket
    int socket4FD;
    //本地IPV6Socket
    int socket6FD;
    //unix域的套接字
    int socketUN;
    //unix域 服務(wù)端 url
    NSURL *socketUrl;
    //狀態(tài)Index
    int stateIndex;
    
    //本機(jī)的IPV4地址
    NSData * connectInterface4;
    //本機(jī)的IPV6地址
    NSData * connectInterface6;
    //本機(jī)unix域地址
    NSData * connectInterfaceUN;
    
    //這個(gè)類的對(duì)Socket的操作都在這個(gè)queue中,串行
    dispatch_queue_t socketQueue;
    
    dispatch_source_t accept4Source;
    dispatch_source_t accept6Source;
    dispatch_source_t acceptUNSource;
    
    //連接timer,GCD定時(shí)器
    dispatch_source_t connectTimer;
    dispatch_source_t readSource;
    dispatch_source_t writeSource;
    dispatch_source_t readTimer;
    dispatch_source_t writeTimer;
   
    //讀寫數(shù)據(jù)包數(shù)組 類似queue,最大限制為5個(gè)包
    NSMutableArray *readQueue;
    NSMutableArray *writeQueue;
    
    //當(dāng)前正在讀寫數(shù)據(jù)包
    GCDAsyncReadPacket *currentRead;
    GCDAsyncWritePacket *currentWrite;
    //當(dāng)前socket未獲取完的數(shù)據(jù)大小
    unsigned long socketFDBytesAvailable;
    
    //全局公用的提前緩沖區(qū)
    GCDAsyncSocketPreBuffer *preBuffer;
        
#if TARGET_OS_IPHONE
    CFStreamClientContext streamContext;
    //讀的數(shù)據(jù)流
    CFReadStreamRef readStream;
    //寫的數(shù)據(jù)流
    CFWriteStreamRef writeStream;
#endif
    //SSL上下文,用來做SSL認(rèn)證
    SSLContextRef sslContext;
    
    //全局公用的SSL的提前緩沖區(qū)
    GCDAsyncSocketPreBuffer *sslPreBuffer;
    size_t sslWriteCachedLength;
    
    //記錄SSL讀取數(shù)據(jù)錯(cuò)誤
    OSStatus sslErrCode;
    //記錄SSL握手的錯(cuò)誤
    OSStatus lastSSLHandshakeError;
    
    //socket隊(duì)列的標(biāo)識(shí)key
    void *IsOnSocketQueueOrTargetQueueKey;
    
    id userData;
    
    //連接備選服務(wù)端地址的延時(shí) (另一個(gè)IPV4或IPV6)
    NSTimeInterval alternateAddressDelay;
}

這個(gè)里定義了一些屬性,可以先簡(jiǎn)單看看注釋,這里我們僅僅先暫時(shí)列出來,給大家混個(gè)眼熟。
在接下來的代碼中,會(huì)大量穿插著這些屬性的使用。所以大家不用覺得困惑,具體作用,我們后面會(huì)一一講清楚的。

接著我們來看看本文方法一--初始化方法:
//層級(jí)調(diào)用
- (id)init
{
     return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}

- (id)initWithSocketQueue:(dispatch_queue_t)sq
{
     return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
     return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
     if((self = [super init]))
     {
          delegate = aDelegate;
          delegateQueue = dq;
          
         //這個(gè)宏是在sdk6.0之后才有的,如果是之前的,則OS_OBJECT_USE_OBJC為0,!0即執(zhí)行if語(yǔ)句
        //對(duì)6.0的適配,如果是6.0以下,則去retain release,6.0之后ARC也管理了GCD
          #if !OS_OBJECT_USE_OBJC
        
          if (dq) dispatch_retain(dq);
          #endif
          
        //創(chuàng)建socket,先都置為 -1
        //本機(jī)的ipv4
          socket4FD = SOCKET_NULL;
        //ipv6
          socket6FD = SOCKET_NULL;
        //應(yīng)該是UnixSocket
          socketUN = SOCKET_NULL;
        //url
          socketUrl = nil;
        //狀態(tài)
          stateIndex = 0;
          
          if (sq)
          {
            //如果scoketQueue是global的,則報(bào)錯(cuò)。斷言必須要一個(gè)非并行queue。
               NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
                        @"The given socketQueue parameter must not be a concurrent queue.");
               NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
                        @"The given socketQueue parameter must not be a concurrent queue.");
               NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                        @"The given socketQueue parameter must not be a concurrent queue.");
               //拿到scoketQueue
               socketQueue = sq;
            //iOS6之下retain
               #if !OS_OBJECT_USE_OBJC
               dispatch_retain(sq);
               #endif
          }
          else
          {
            //沒有的話創(chuàng)建一個(gè),  名字為:GCDAsyncSocket,串行
               socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
          }
          
          // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
          // From the documentation:
          //
          // > Keys are only compared as pointers and are never dereferenced.
          // > Thus, you can use a pointer to a static variable for a specific subsystem or
          // > any other value that allows you to identify the value uniquely.
          //
          // We're just going to use the memory address of an ivar.
          // Specifically an ivar that is explicitly named for our purpose to make the code more readable.
          //
          // However, it feels tedious (and less readable) to include the "&" all the time:
          // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
          //
          // So we're going to make it so it doesn't matter if we use the '&' or not,
          // by assigning the value of the ivar to the address of the ivar.
          // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
          
        
        //比如原來為   0X123 -> NULL 變成  0X222->0X123->NULL
        //自己的指針等于自己原來的指針,成二級(jí)指針了  看了注釋是為了以后省略&,讓代碼更可讀?
          IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
          
        
          void *nonNullUnusedPointer = (__bridge void *)self;
        
        //dispatch_queue_set_specific給當(dāng)前隊(duì)里加一個(gè)標(biāo)識(shí) dispatch_get_specific當(dāng)前線程取出這個(gè)標(biāo)識(shí),判斷是不是在這個(gè)隊(duì)列
        //這個(gè)key的值其實(shí)就是一個(gè)一級(jí)指針的地址  ,第三個(gè)參數(shù)把自己傳過去了,上下文對(duì)象?第4個(gè)參數(shù),為銷毀的時(shí)候用的,可以指定一個(gè)函數(shù)
          dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
          //讀的數(shù)組 限制為5
          readQueue = [[NSMutableArray alloc] initWithCapacity:5];
          currentRead = nil;
          
        //寫的數(shù)組,限制5
          writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
          currentWrite = nil;
          
        //設(shè)置大小為 4kb
          preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
    
#pragma mark alternateAddressDelay??
        //交替地址延時(shí)?? wtf
        alternateAddressDelay = 0.3;
     }
     return self;
}

詳細(xì)的細(xì)節(jié)可以看看注釋,這里初始化了一些屬性:

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

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

//本機(jī)的ipv4
socket4FD = SOCKET_NULL;
//ipv6
socket6FD = SOCKET_NULL;
//UnixSocket
socketUN = SOCKET_NULL;

其中值得一提的是第三種:UnixSocket,這個(gè)是用于Unix Domin Socket通信用的。
那么什么是Unix Domain Socket呢?
原來它是在socket的框架上發(fā)展出一種IPC(進(jìn)程間通信)機(jī)制,雖然網(wǎng)絡(luò)socket也可用于同一臺(tái)主機(jī)的進(jìn)程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC 更有效率 :

  • 不需要經(jīng)過網(wǎng)絡(luò)協(xié)議棧
  • 不需要打包拆包、計(jì)算校驗(yàn)和、維護(hù)序號(hào)和應(yīng)答等,只是將應(yīng)用層數(shù)據(jù)從一個(gè)進(jìn)程拷貝到另一個(gè)進(jìn)程。這是因?yàn)椋琁PC機(jī)制本質(zhì)上是可靠的通訊,而網(wǎng)絡(luò)協(xié)議是為不可靠的通訊設(shè)計(jì)的。UNIX Domain Socket也提供面向流和面向數(shù)據(jù)包兩種API接口,類似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會(huì)丟失也不會(huì)順序錯(cuò)亂。

基本上它是當(dāng)今應(yīng)用于IPC最主流的方式。至于它到底和普通的socket通信實(shí)現(xiàn)起來有什么區(qū)別,別著急,我們接著往下看。

3.生成了一個(gè)socketQueue,這個(gè)queue是串行的,接下來我們看代碼就會(huì)知道它貫穿于這個(gè)類的所有地方。所有對(duì)socket以及一些內(nèi)部數(shù)據(jù)的相關(guān)操作,都需要在這個(gè)串行queue中進(jìn)行。這樣使得整個(gè)類沒有加一個(gè)鎖,就保證了整個(gè)類的線程安全。

4.創(chuàng)建了兩個(gè)讀寫隊(duì)列(本質(zhì)數(shù)組),接下來我們所有的讀寫任務(wù),都會(huì)先追加在這個(gè)隊(duì)列最后,然后每次取出隊(duì)列中最前面的任務(wù),進(jìn)行處理。

5.創(chuàng)建了一個(gè)全局的數(shù)據(jù)緩沖區(qū):preBuffer,我們所操作的數(shù)據(jù),大部分都是要先存入這個(gè)preBuffer中,然后再?gòu)?code>preBuffer取出進(jìn)行處理的。

6.初始化了一個(gè)交替延時(shí)變量:alternateAddressDelay,這個(gè)變量先簡(jiǎn)單的理解下:就是進(jìn)行另一個(gè)服務(wù)端地址請(qǐng)求的延時(shí)。后面我們一講到,大家就明白了。

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

分割圖.png

接著我們有socket了,我們?nèi)绻强蛻舳耍托枰?code>connect服務(wù)器。

又或者我們是服務(wù)端的話,就需要去bind端口,并且accept,等待客戶端的連接。(基本上也沒有用iOS來做服務(wù)端的吧...)

這里我們先作為客戶端來看看connect
  • connect.png

其中和connect相關(guān)的方法就這么多,我們一般這么來連接到服務(wù)端:

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

也就是我們?cè)诮貓D中選中的方法,那我們就從這個(gè)方法作為起點(diǎn),開始講起吧。

本文方法二--connect總方法
/逐級(jí)調(diào)用
- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
{
    return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
}

- (BOOL)connectToHost:(NSString *)host
               onPort:(uint16_t)port
          withTimeout:(NSTimeInterval)timeout
                error:(NSError **)errPtr
{
    return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
}

//多一個(gè)inInterface,本機(jī)地址
- (BOOL)connectToHost:(NSString *)inHost
               onPort:(uint16_t)port
         viaInterface:(NSString *)inInterface
          withTimeout:(NSTimeInterval)timeout
                error:(NSError **)errPtr
{
    //{} 跟蹤當(dāng)前行為
    LogTrace();
    
    // Just in case immutable objects were passed
    //拿到host ,copy防止值被修改
    NSString *host = [inHost copy];
    //interface?接口?
    NSString *interface = [inInterface copy];
    
    //聲明兩個(gè)__block的
    __block BOOL result = NO;
    //error信息
    __block NSError *preConnectErr = nil;
    
    //gcdBlock ,都包裹在自動(dòng)釋放池中
    dispatch_block_t block = ^{ @autoreleasepool {
        
        // Check for problems with host parameter
        
        if ([host length] == 0)
        {
            NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
            preConnectErr = [self badParamError:msg];
            
            //其實(shí)就是return,大牛的代碼真是充滿逼格
            return_from_block;
        }
        
        // Run through standard pre-connect checks
        //一個(gè)前置的檢查,如果沒通過返回,這個(gè)檢查里,如果interface有值,則會(huì)將本機(jī)的IPV4 IPV6的 address設(shè)置上。
        if (![self preConnectWithInterface:interface error:&preConnectErr])
        {
            return_from_block;
        }
        
        // We've made it past all the checks.
        // It's time to start the connection process.
        //flags 做或等運(yùn)算。 flags標(biāo)識(shí)為開始Socket連接
        flags |= kSocketStarted;
        
        //又是一個(gè){}? 只是為了標(biāo)記么?
        LogVerbose(@"Dispatching DNS lookup...");
        
        // It's possible that the given host parameter is actually a NSMutableString.
        //很可能給我們的服務(wù)端的參數(shù)是一個(gè)可變字符串
        // So we want to copy it now, within this block that will be executed synchronously.
        //所以我們需要copy,在Block里同步的執(zhí)行
        // This way the asynchronous lookup block below doesn't have to worry about it changing.
        //這種基于Block的異步查找,不需要擔(dān)心它被改變
        
        //copy,防止改變
        NSString *hostCpy = [host copy];
        
        //拿到狀態(tài)
        int aStateIndex = stateIndex;
        __weak GCDAsyncSocket *weakSelf = self;
        
        //全局Queue
        dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //異步執(zhí)行
        dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
            //忽視循環(huán)引用
        #pragma clang diagnostic push
        #pragma clang diagnostic warning "-Wimplicit-retain-self"
            
            //查找錯(cuò)誤
            NSError *lookupErr = nil;
            //server地址數(shù)組(包含IPV4 IPV6的地址  sockaddr_in6、sockaddr_in類型)
            NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
            
            //strongSelf
            __strong GCDAsyncSocket *strongSelf = weakSelf;
            
            //完整Block安全形態(tài),在加個(gè)if
            if (strongSelf == nil) return_from_block;
            
            //如果有錯(cuò)
            if (lookupErr)
            {
                //用cocketQueue
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    //一些錯(cuò)誤處理,清空一些數(shù)據(jù)等等
                    [strongSelf lookup:aStateIndex didFail:lookupErr];
                }});
            }
            //正常
            else
            {
                
                NSData *address4 = nil;
                NSData *address6 = nil;
                //遍歷地址數(shù)組
                for (NSData *address in addresses)
                {
                    //判斷address4為空,且address為IPV4
                    if (!address4 && [[self class] isIPv4Address:address])
                    {
                        address4 = address;
                    }
                    //判斷address6為空,且address為IPV6
                    else if (!address6 && [[self class] isIPv6Address:address])
                    {
                        address6 = address;
                    }
                }
                //異步去發(fā)起連接
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    
                    [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
                }});
            }
            
        #pragma clang diagnostic pop
        }});
        
        
        //開啟連接超時(shí)
        [self startConnectTimeout:timeout];
        
        result = YES;
    }};
    //在socketQueue中執(zhí)行這個(gè)Block
    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
        block();
    //否則同步的調(diào)起這個(gè)queue去執(zhí)行
    else
        dispatch_sync(socketQueue, block);
    
    //如果有錯(cuò)誤,賦值錯(cuò)誤
    if (errPtr) *errPtr = preConnectErr;
    //把連接是否成功的result返回
    return result;
}

這個(gè)方法非常長(zhǎng),它主要做了以下幾件事:

  • 首先我們需要說一下的是,整個(gè)類大量的會(huì)出現(xiàn)LogTrace()類似這樣的宏,我們點(diǎn)進(jìn)去發(fā)現(xiàn)它的本質(zhì)只是一個(gè){},什么事都沒做。

原來這些宏是為了追蹤當(dāng)前執(zhí)行的流程用的,它被定義在一個(gè)大的#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

而此時(shí)因?yàn)?code>GCDAsyncSocketLoggingEnabled默認(rèn)為0,所以僅僅是一個(gè){}。當(dāng)標(biāo)記為1時(shí),這些宏就可以用來輸出我們當(dāng)前的業(yè)務(wù)流程,極大的方便了我們的調(diào)試過程。

  • 接著我們回到正題上,我們定義了一個(gè)Block,所有的連接操作都被包裹在這個(gè)Block中。我們做了如下判斷:
    //在socketQueue中執(zhí)行這個(gè)Block
    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
        block();
    //否則同步的調(diào)起這個(gè)queue去執(zhí)行
    else
        dispatch_sync(socketQueue, block);

保證這個(gè)連接操作一定是在我們的socketQueue中,而且還是以串行同步的形式去執(zhí)行,規(guī)避了線程安全的問題。

  • 接著把Block中連接過程產(chǎn)生的錯(cuò)誤進(jìn)行賦值,并且把連接的結(jié)果返回出去
//如果有錯(cuò)誤,賦值錯(cuò)誤
    if (errPtr) *errPtr = preConnectErr;
    //把連接是否成功的result返回
    return result;

接著來看這個(gè)方法聲明的Block內(nèi)部,也就是進(jìn)行連接的真正主題操作,這個(gè)連接過程將會(huì)調(diào)用許多函數(shù),一環(huán)扣一環(huán),我會(huì)盡可能用最清晰、詳盡的語(yǔ)言來描述...

1.這個(gè)Block首先做了一些錯(cuò)誤的判斷,并調(diào)用了一些錯(cuò)誤生成的方法。類似:
if ([host length] == 0)
{
     NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
     preConnectErr = [self badParamError:msg];
     
  //其實(shí)就是return,大牛的代碼真是充滿逼格
     return_from_block;
}
//用該字符串生成一個(gè)錯(cuò)誤,錯(cuò)誤的域名,錯(cuò)誤的參數(shù)
- (NSError *)badParamError:(NSString *)errMsg
{
    NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
    
    return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
}
2.接著做了一個(gè)前置的錯(cuò)誤檢查:
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
     return_from_block;
}

這個(gè)檢查方法,如果沒通過返回NO。并且如果interface有值,則會(huì)將本機(jī)的IPV4 IPV6的 address設(shè)置上。即我們之前提到的這兩個(gè)屬性:

  //本機(jī)的IPV4地址
NSData * connectInterface4;
//本機(jī)的IPV6地址
NSData * connectInterface6;

我們來看看這個(gè)前置檢查方法:

本文方法三--前置檢查方法
//在連接之前的接口檢查,一般我們傳nil  interface本機(jī)的IP 端口等等
- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
{
    //先斷言,如果當(dāng)前的queue不是初始化quueue,直接報(bào)錯(cuò)
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    
    //無代理
    if (delegate == nil) // Must have delegate set
    {
        if (errPtr)
        {
            NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
            *errPtr = [self badConfigError:msg];
        }
        return NO;
    }
    //沒有代理queue
    if (delegateQueue == NULL) // Must have delegate queue set
    {
        if (errPtr)
        {
            NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
            *errPtr = [self badConfigError:msg];
        }
        return NO;
    }
    
    //當(dāng)前不是非連接狀態(tài)
    if (![self isDisconnected]) // Must be disconnected
    {
        if (errPtr)
        {
            NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
            *errPtr = [self badConfigError:msg];
        }
        return NO;
    }
    
    //判斷是否支持IPV4 IPV6  &位與運(yùn)算,因?yàn)槊杜e是用  左位移<<運(yùn)算定義的,所以可以用來判斷 config包不包含某個(gè)枚舉。因?yàn)橐粋€(gè)值可能包含好幾個(gè)枚舉值,所以這時(shí)候不能用==來判斷,只能用&來判斷
    BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
    BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
    
    //是否都不支持
    if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
    {
        if (errPtr)
        {
            NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
            *errPtr = [self badConfigError:msg];
        }
        return NO;
    }
    
    //如果有interface,本機(jī)地址
    if (interface)
    {
        NSMutableData *interface4 = nil;
        NSMutableData *interface6 = nil;
        
        //得到本機(jī)的IPV4 IPV6地址
        [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
        
        //如果兩者都為nil
        if ((interface4 == nil) && (interface6 == nil))
        {
            if (errPtr)
            {
                NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
                *errPtr = [self badParamError:msg];
            }
            return NO;
        }
        
        if (isIPv4Disabled && (interface6 == nil))
        {
            if (errPtr)
            {
                NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
                *errPtr = [self badParamError:msg];
            }
            return NO;
        }
        
        if (isIPv6Disabled && (interface4 == nil))
        {
            if (errPtr)
            {
                NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
                *errPtr = [self badParamError:msg];
            }
            return NO;
        }
        //如果都沒問題,則賦值
        connectInterface4 = interface4;
        connectInterface6 = interface6;
    }
    
    // Clear queues (spurious read/write requests post disconnect)
    //清除queue(假的讀寫請(qǐng)求 ,提交斷開連接)
    //讀寫Queue清除
    [readQueue removeAllObjects];
    [writeQueue removeAllObjects];
    
    return YES;
}

又是非常長(zhǎng)的一個(gè)方法,但是這個(gè)方法還是非常好讀的。

  • 主要是對(duì)連接前的一個(gè)屬性參數(shù)的判斷,如果不齊全的話,則填充錯(cuò)誤指針,并且返回NO。

  • 在這里如果我們interface這個(gè)參數(shù)不為空話,我們會(huì)額外多執(zhí)行一些操作。
    首先來講講這個(gè)參數(shù)是什么,簡(jiǎn)單來講,這個(gè)就是我們?cè)O(shè)置的本機(jī)IP+端口號(hào)。照理來說我們是不需要去設(shè)置這個(gè)參數(shù)的,默認(rèn)的為localhost(127.0.0.1)本機(jī)地址。而端口號(hào)會(huì)在本機(jī)中取一個(gè)空閑可用的端口。
    而我們一旦設(shè)置了這個(gè)參數(shù),就會(huì)強(qiáng)制本地IP和端口為我們指定的。其實(shí)這樣設(shè)置反而不好,其實(shí)大家也能想明白,這里端口號(hào)如果我們寫死,萬一被其他進(jìn)程給占用了。那么肯定是無法連接成功的。
    所以就有了我們做IM的時(shí)候,一般是不會(huì)去指定客戶端bind某一個(gè)端口。而是用系統(tǒng)自動(dòng)去選擇。

  • 我們最后清空了當(dāng)前讀寫queue中,所有的任務(wù)。

至于有interface,我們所做的額外操作是什么呢,我們接下來看看這個(gè)方法:

本文方法四--本地地址綁定方法
- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
                    address6:(NSMutableData **)interfaceAddr6Ptr
             fromDescription:(NSString *)interfaceDescription
                        port:(uint16_t)port
{
    NSMutableData *addr4 = nil;
    NSMutableData *addr6 = nil;
    
    NSString *interface = nil;
    
    //先用:分割
    NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
    if ([components count] > 0)
    {
        NSString *temp = [components objectAtIndex:0];
        if ([temp length] > 0)
        {
            interface = temp;
        }
    }
    if ([components count] > 1 && port == 0)
    {
        //拿到port strtol函數(shù),將一個(gè)字符串,根據(jù)base參數(shù)轉(zhuǎn)成長(zhǎng)整型,如base值為10則采用10進(jìn)制,若base值為16則采用16進(jìn)制
        long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
        //UINT16_MAX,65535最大端口號(hào)
        if (portL > 0 && portL <= UINT16_MAX)
        {
            port = (uint16_t)portL;
        }
    }
    
    //為空則自己創(chuàng)建一個(gè) 0x00000000 ,全是0 ,為線路地址
    //如果端口為0 通常用于分析操作系統(tǒng)。這一方法能夠工作是因?yàn)樵谝恍┫到y(tǒng)中“0”是無效端口,當(dāng)你試圖使用通常的閉合端口連接它時(shí)將產(chǎn)生不同的結(jié)果。一種典型的掃描,使用IP地址為0.0.0.0,設(shè)置ACK位并在以太網(wǎng)層廣播。
    if (interface == nil)
    {
        
        struct sockaddr_in sockaddr4;
        
        //memset作用是在一段內(nèi)存塊中填充某個(gè)給定的值,它是對(duì)較大的結(jié)構(gòu)體或數(shù)組進(jìn)行清零操作的一種最快方法
        
        //memset(void *s,int ch,size_t n);函數(shù),第一個(gè)參數(shù)為指針地址,第二個(gè)為設(shè)置值,第三個(gè)為連續(xù)設(shè)置的長(zhǎng)度(大小)
        memset(&sockaddr4, 0, sizeof(sockaddr4));
        //結(jié)構(gòu)體長(zhǎng)度
        sockaddr4.sin_len         = sizeof(sockaddr4);
        //addressFamily IPv4(AF_INET) 或 IPv6(AF_INET6)。
        sockaddr4.sin_family      = AF_INET;
        //端口號(hào) htons將主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序  16位
        sockaddr4.sin_port        = htons(port);
        //htonl ,將INADDR_ANY:0.0.0.0,不確定地址,或者任意地址  htonl 32位。 也是轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
      
        //ipv4 32位  4個(gè)字節(jié)    INADDR_ANY,0x00000000 (16進(jìn)制,一個(gè)0代表4位,8個(gè)0就是32位) =   4個(gè)字節(jié)的
        sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
        struct sockaddr_in6 sockaddr6;
        memset(&sockaddr6, 0, sizeof(sockaddr6));
        
        sockaddr6.sin6_len       = sizeof(sockaddr6);
        //ipv6
        sockaddr6.sin6_family    = AF_INET6;
        //port
        sockaddr6.sin6_port      = htons(port);
        
        //共128位
        sockaddr6.sin6_addr      = in6addr_any;
        
        //把這兩個(gè)結(jié)構(gòu)體轉(zhuǎn)成data
        addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
        addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
    }
    //如果localhost、loopback 回環(huán)地址,虛擬地址,路由器工作它就存在。一般用來標(biāo)識(shí)路由器
    //這兩種的話就賦值為127.0.0.1,端口為port
    else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
    {
        // LOOPBACK address
        
        //ipv4
        struct sockaddr_in sockaddr4;
        memset(&sockaddr4, 0, sizeof(sockaddr4));
        
        sockaddr4.sin_len         = sizeof(sockaddr4);
        sockaddr4.sin_family      = AF_INET;
        sockaddr4.sin_port        = htons(port);
        
        //#define   INADDR_LOOPBACK     (u_int32_t)0x7f000001
        //7f000001->1111111 00000000 00000000 00000001->127.0.0.1
        sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        
        //ipv6
        struct sockaddr_in6 sockaddr6;
        memset(&sockaddr6, 0, sizeof(sockaddr6));
        
        sockaddr6.sin6_len       = sizeof(sockaddr6);
        sockaddr6.sin6_family    = AF_INET6;
        sockaddr6.sin6_port      = htons(port);

        sockaddr6.sin6_addr      = in6addr_loopback;
        //賦值
        addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
        addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
    }
    //非localhost、loopback,去獲取本機(jī)IP,看和傳進(jìn)來Interface是同名或者同IP,相同才給賦端口號(hào),把數(shù)據(jù)封裝進(jìn)Data。否則為nil
    else
    {
        //轉(zhuǎn)成cString
        const char *iface = [interface UTF8String];
    
        //定義結(jié)構(gòu)體指針,這個(gè)指針是本地IP
        struct ifaddrs *addrs;
        const struct ifaddrs *cursor;
        
        //獲取到本機(jī)IP,為0說明成功了
        if ((getifaddrs(&addrs) == 0))
        {
            //賦值
            cursor = addrs;
            //如果IP不為空,則循環(huán)鏈表去設(shè)置
            while (cursor != NULL)
            {
                //如果 addr4 IPV4地址為空,而且地址類型為IPV4
                if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
                {
                    // IPv4
                    
                    struct sockaddr_in nativeAddr4;
                    //memcpy內(nèi)存copy函數(shù),把src開始到size的字節(jié)數(shù)copy到 dest中
                    memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
                    
                    //比較兩個(gè)字符串是否相同,本機(jī)的IP名,和接口interface是否相同
                    if (strcmp(cursor->ifa_name, iface) == 0)
                    {
                        // Name match
                        //相同則賦值 port
                        nativeAddr4.sin_port = htons(port);
                        //用data封號(hào)IPV4地址
                        addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
                    }
                    //本機(jī)IP名和interface不相同
                    else
                    {
                        //聲明一個(gè)IP 16位的數(shù)組
                        char ip[INET_ADDRSTRLEN];
                     
                        //這里是轉(zhuǎn)成了10進(jìn)制。。(因?yàn)楂@取到的是二進(jìn)制IP)
                        const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
                        
                        //如果conversion不為空,說明轉(zhuǎn)換成功而且 ,比較轉(zhuǎn)換后的IP,和interface是否相同
                        if ((conversion != NULL) && (strcmp(ip, iface) == 0))
                        {
                            // IP match
                            //相同則賦值 port
                            nativeAddr4.sin_port = htons(port);
                            
                            addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
                        }
                    }
                }
                //IPV6 一樣
                else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
                {
                    // IPv6
                    
                    struct sockaddr_in6 nativeAddr6;
                    memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
                    
                    if (strcmp(cursor->ifa_name, iface) == 0)
                    {
                        // Name match
                        
                        nativeAddr6.sin6_port = htons(port);
                        
                        addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
                    }
                    else
                    {
                        char ip[INET6_ADDRSTRLEN];
                        
                        const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
                        
                        if ((conversion != NULL) && (strcmp(ip, iface) == 0))
                        {
                            // IP match
                            
                            nativeAddr6.sin6_port = htons(port);
                            
                            addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
                        }
                    }
                }
                
                //指向鏈表下一個(gè)addr
                cursor = cursor->ifa_next;
            }
            //和getifaddrs對(duì)應(yīng),釋放這部分內(nèi)存
            freeifaddrs(addrs);
        }
    }
    //如果這兩個(gè)二級(jí)指針存在,則取成一級(jí)指針,把a(bǔ)ddr4賦值給它
    if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
    if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;

這個(gè)方法中,主要是大量的socket相關(guān)的函數(shù)的調(diào)用,會(huì)顯得比較難讀一點(diǎn),其實(shí)簡(jiǎn)單來講就做了這么一件事:
interface變成進(jìn)行socket操作所需要的地址結(jié)構(gòu)體,然后把地址結(jié)構(gòu)體包裹在NSMutableData中。

這里,為了讓大家能更容易理解,我把這個(gè)方法涉及到的socket相關(guān)函數(shù)以及宏(按照調(diào)用順序)都列出來:

//拿到port strtol函數(shù),將一個(gè)字符串,根據(jù)base參數(shù)轉(zhuǎn)成長(zhǎng)整型,
//如base值為10則采用10進(jìn)制,若base值為16則采用16進(jìn)制
long  strtol(const char *__str, char **__endptr, int __base);

//作用是在一段內(nèi)存塊中填充某個(gè)給定的值,它是對(duì)較大的結(jié)構(gòu)體或數(shù)組進(jìn)行清零操作的一種最快方法
//第一個(gè)參數(shù)為指針地址,第二個(gè)為設(shè)置值,第三個(gè)為連續(xù)設(shè)置的長(zhǎng)度(大小)
memset(void *s,int ch,size_t n);

//最大端口號(hào)
#define UINT16_MAX        65535

//作用是把主機(jī)字節(jié)序轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序 
htons() //參數(shù)16位
htonl() //參數(shù)32位
//獲取占用內(nèi)存大小
sizeof()
//比較兩個(gè)指針,是否相同 相同返回0
int   strcmp(const char *__s1, const char *__s2)


//內(nèi)存copu函數(shù),把src開始到len的字節(jié)數(shù)copy到 dest中
memcpy(dest, src, len)   

//inet_pton和inet_ntop這2個(gè)IP地址轉(zhuǎn)換函數(shù),可以在將IP地址在“點(diǎn)分十進(jìn)制”和“二進(jìn)制整數(shù)”之間轉(zhuǎn)換
//參數(shù)socklen_t cnt,他是所指向緩存區(qū)dst的大小,避免溢出,如果緩存區(qū)太小無法存儲(chǔ)地址的值,則返回一個(gè)空指針,并將errno置為ENOSPC
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);

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

還有一些用到的作為參數(shù)的結(jié)構(gòu)體:

//socket通信用的 IPV4地址結(jié)構(gòu)體 
struct sockaddr_in { 
     __uint8_t sin_len;      //整個(gè)結(jié)構(gòu)體大小
     sa_family_t    sin_family;      //協(xié)議族,IPV4?IPV6
     in_port_t sin_port;      //端口
     struct    in_addr sin_addr;      //IP地址
     char      sin_zero[8];     //空的占位符,為了和其他地址結(jié)構(gòu)體保持一致大小,方便轉(zhuǎn)化
};
//IPV6地址結(jié)構(gòu)體,和上面的類似
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 */
};

//用來獲取本機(jī)IP的參數(shù)結(jié)構(gòu)體
struct ifaddrs {
    //指向鏈表的下一個(gè)成員
    struct ifaddrs  *ifa_next;
    //接口名稱
    char       *ifa_name;
    //接口標(biāo)識(shí)位(比如當(dāng)IFF_BROADCAST或IFF_POINTOPOINT設(shè)置到此標(biāo)識(shí)位時(shí),影響聯(lián)合體變量ifu_broadaddr存儲(chǔ)廣播地址或ifu_dstaddr記錄點(diǎn)對(duì)點(diǎn)地址)
    unsigned int     ifa_flags;
    //接口地址
    struct sockaddr *ifa_addr;
    //存儲(chǔ)該接口的子網(wǎng)掩碼;
    struct sockaddr *ifa_netmask;
   
    //點(diǎn)對(duì)點(diǎn)的地址
    struct sockaddr *ifa_dstaddr;
    //ifa_data存儲(chǔ)了該接口協(xié)議族的特殊信息,它通常是NULL(一般不關(guān)注他)。
    void       *ifa_data;
};

這一段內(nèi)容算是比較枯澀了,但是也是了解socket編程必經(jīng)之路。

這里提到了網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序。我們創(chuàng)建socket之前,必須把port和host這些參數(shù)轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序。那么為什么要這么做呢?

不同的CPU有不同的字節(jié)序類型 這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序 這個(gè)叫做主機(jī)序
最常見的有兩種
1. Little endian:將低序字節(jié)存儲(chǔ)在起始地址
2. Big endian:將高序字節(jié)存儲(chǔ)在起始地址

這樣如果我們到網(wǎng)絡(luò)中,就無法得知互相的字節(jié)序是什么了,所以我們就必須統(tǒng)一一套排序,這樣網(wǎng)絡(luò)字節(jié)序就有它存在的必要了。

網(wǎng)絡(luò)字節(jié)順序是TCP/IP中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的CPU類型、操作系統(tǒng)等無關(guān)。從而可以保證數(shù)據(jù)在不同主機(jī)之間傳輸時(shí)能夠被正確解釋。網(wǎng)絡(luò)字節(jié)順序采用big endian排序方式。

大家感興趣可以到這篇文章中去看看:網(wǎng)絡(luò)字節(jié)序與主機(jī)字節(jié)序

除此之外比較重要的就是這幾個(gè)地址結(jié)構(gòu)體了。它定義了我們當(dāng)前socket的地址信息。包括IP、Port、長(zhǎng)度、協(xié)議族等等。當(dāng)然socket中標(biāo)識(shí)為地址的結(jié)構(gòu)體不止這3種,等我們后續(xù)代碼來補(bǔ)充。

大家了解了我們上述說的知識(shí)點(diǎn),這個(gè)方法也就不難度了。這個(gè)方法主要是做了本機(jī)IPV4IPV6地址的創(chuàng)建和綁定。當(dāng)然這里分了幾種情況:

  1. interface為空的,我們作為客戶端不會(huì)出現(xiàn)這種情況。注意之前我們是這個(gè)參數(shù)不為空才會(huì)調(diào)入這個(gè)方法的。
    而這個(gè)一般是用于做服務(wù)端監(jiān)聽用的,這里的處理是給本機(jī)地址綁定0地址(任意地址)。那么這里這么做作用是什么呢?引用一個(gè)應(yīng)用場(chǎng)景來說明:

如果你的服務(wù)器有多個(gè)網(wǎng)卡(每個(gè)網(wǎng)卡上有不同的IP地址),而你的服務(wù)(不管是在udp端口上偵聽,還是在tcp端口上偵聽),出于某種原因:可能是你的服務(wù)器操作系統(tǒng)可能隨時(shí)增減IP地址,也有可能是為了省去確定服務(wù)器上有什么網(wǎng)絡(luò)端口(網(wǎng)卡)的麻煩 —— 可以要在調(diào)用bind()的時(shí)候,告訴操作系統(tǒng):“我需要在 yyyy 端口上偵聽,所有發(fā)送到服務(wù)器的這個(gè)端口,不管是哪個(gè)網(wǎng)卡/哪個(gè)IP地址接收到的數(shù)據(jù),都是我處理的。”這時(shí)候,服務(wù)器程序則在0.0.0.0這個(gè)地址上進(jìn)行偵聽。

  1. 如果interfacelocalhost或者loopback則把IP設(shè)置為127.0.0.1,這里localhost我們大家都知道。那么什么是loopback呢?
    loopback地址叫做回環(huán)地址,他不是一個(gè)物理接口上的地址,他是一個(gè)虛擬的一個(gè)地址,只要路由器在工作,這個(gè)地址就存在.它是路由器的唯一標(biāo)識(shí)。
    更詳細(xì)的內(nèi)容可以看看百科:loopback

  2. 如果是一個(gè)其他的地址,我們會(huì)去使用getifaddrs()函數(shù)得到本機(jī)地址。然后去對(duì)比本機(jī)名或者本機(jī)IP。有一個(gè)能相同,我們就認(rèn)為該地址有效,就進(jìn)行IPV4和IPV6綁定。否則什么都不做。

至此這個(gè)本機(jī)地址綁定我們就做完了,我們前面也說過,一般我們作為客戶端,是不需要做這一步的。如果我們不綁定,系統(tǒng)會(huì)自己綁定本機(jī)IP,并且選擇一個(gè)空閑可用的端口。所以這個(gè)方法是iOS用來作為服務(wù)端調(diào)用的。

方法三--前置檢查、方法四--本機(jī)地址綁定都說完了,我們繼續(xù)接著之前的方法二往下看:

之前講到第3點(diǎn)了:
3.這里把flag標(biāo)記為kSocketStarted:

flags |= kSocketStarted;

源碼中大量的運(yùn)用了3個(gè)位運(yùn)算符:分別是或(|)、與(&)、取反(~)、運(yùn)算符。 運(yùn)用這個(gè)標(biāo)記的好處也很明顯,可以很簡(jiǎn)單的標(biāo)記當(dāng)前的狀態(tài),并且因?yàn)閒lags所指向的枚舉值是用左位移的方式:

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可以通過|的方式復(fù)合橫跨多個(gè)狀態(tài),并且運(yùn)算也非常輕量級(jí),好處很多,所有的狀態(tài)標(biāo)記的意義可以在注釋中清晰的看出,這里把狀態(tài)標(biāo)記為socket已經(jīng)開始連接了。

4.然后我們調(diào)用了一個(gè)全局queue,異步的調(diào)用連接,這里又做了兩件事:

  • 第一步是拿到我們需要連接的服務(wù)端server的地址數(shù)組:
//server地址數(shù)組(包含IPV4 IPV6的地址  sockaddr_in6、sockaddr_in類型)
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
  • 第二步是做一些錯(cuò)誤判斷,并且把地址信息賦值到address4address6中去,然后異步調(diào)用回socketQueue去用另一個(gè)方法去發(fā)起連接:
//異步去發(fā)起連接
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
     
     [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});

在這個(gè)方法中我們可以看到作者這里把創(chuàng)建server地址這些費(fèi)時(shí)的邏輯操作放在了異步線程中并發(fā)進(jìn)行。然后得到數(shù)據(jù)之后又回到了我們的socketQueue發(fā)起下一步的連接。

然后這里又是兩個(gè)很大塊的分支,首先我們來看看server地址的獲取:

本文方法五--創(chuàng)建服務(wù)端server地址數(shù)據(jù):
//根據(jù)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)];
          
        //兩個(gè)添加進(jìn)數(shù)組
          addresses = [NSMutableArray arrayWithCapacity:2];
          [addresses addObject:address4];
          [addresses addObject:address6];
     }
     else
     {
        //拿到port String
          NSString *portStr = [NSString stringWithFormat:@"%hu", port];
          

        
        //定義三個(gè)addrInfo  是一個(gè)sockaddr結(jié)構(gòu)的鏈表而不是一個(gè)地址清單
        
          struct addrinfo hints, *res, *res0;
          
        //初始化為0
          memset(&hints, 0, sizeof(hints));
        
        //相當(dāng)于 AF_UNSPEC ,返回的是適用于指定主機(jī)名和服務(wù)名且適合任何協(xié)議族的地址。
          hints.ai_family   = PF_UNSPEC;
          hints.ai_socktype = SOCK_STREAM;
          hints.ai_protocol = IPPROTO_TCP;
          

        //根據(jù)host port,去獲取地址信息。
    
          int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
          
        //出錯(cuò)
          if (gai_error)
          {   //獲取到錯(cuò)誤
               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++;
                    }
               }
               //生成一個(gè)地址數(shù)組,數(shù)組為capacity大小
               addresses = [NSMutableArray arrayWithCapacity:capacity];
               
            //再去遍歷,為什么不一次遍歷完,僅僅是為了限制數(shù)組的大小?
               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.
                         //加到數(shù)組中
                         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.
                    //強(qiáng)轉(zhuǎn)
                         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)) {
                        //賦值,用傳進(jìn)來的port
                                 *portPtr = htons(port);
                         }
                    //添加到數(shù)組
                         NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
                         [addresses addObject:address6];
                    }
               }
            //對(duì)應(yīng)getaddrinfo 釋放內(nèi)存
               freeaddrinfo(res0);
               
            //如果地址里一個(gè)沒有,報(bào)錯(cuò) EAI_FAIL:名字解析中不可恢復(fù)的失敗
               if ([addresses count] == 0)
               {
                    error = [self gaiError:EAI_FAIL];
               }
          }
     }
     //賦值錯(cuò)誤
     if (errPtr) *errPtr = error;
    //返回地址
     return addresses;
}

這個(gè)方法根據(jù)host進(jìn)行了劃分:

  1. 如果hostlocalhost或者loopback,則按照我們之前綁定本機(jī)地址那一套生成地址的方式,去生成IPV4和IPV6的地址,并且用NSData包裹住這個(gè)地址結(jié)構(gòu)體,裝在NSMutableArray中。
  2. 不是本機(jī)地址,那么我們就需要根據(jù)host和port去創(chuàng)建地址了,這里用到的是這么一個(gè)函數(shù):
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

這個(gè)函數(shù)主要的作用是:根據(jù)hostname(IP)service(port),去獲取地址信息,并且把地址信息傳遞到result中。
而hints這個(gè)參數(shù)可以是一個(gè)空指針,也可以是一個(gè)指向某個(gè)addrinfo結(jié)構(gòu)體的指針,如果填了,其實(shí)它就是一個(gè)配置參數(shù),返回的地址信息會(huì)和這個(gè)配置參數(shù)的內(nèi)容有關(guān),如下例:

舉例來說:指定的服務(wù)既可支持TCP也可支持UDP,所以調(diào)用者可以把hints結(jié)構(gòu)中的ai_socktype成員設(shè)置成SOCK_DGRAM使得返回的僅僅是適用于數(shù)據(jù)報(bào)套接口的信息。

這里我們可以看到result和hints這兩個(gè)參數(shù)指針指向的都是一個(gè)addrinfo的結(jié)構(gòu)體,這是我們繼上面以來看到的第4種地址結(jié)構(gòu)體了。它的定義如下:

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 */
};

我們可以看到它其中包括了一個(gè)IPV4的結(jié)構(gòu)體地址ai_addr,還有一個(gè)指向下一個(gè)同類型數(shù)據(jù)節(jié)點(diǎn)的指針ai_next
其他參數(shù)和之前的地址結(jié)構(gòu)體一些參數(shù)作用類似,大家可以對(duì)著注釋很好理解,或者仍有疑惑可以看看這篇:
socket編程之a(chǎn)ddrinfo結(jié)構(gòu)體與getaddrinfo函數(shù)
這里講講ai_next這個(gè)指針,因?yàn)槲覀兪侨カ@取server端的地址,所以很可能有不止一個(gè)地址,比如IPV4、IPV6,又或者我們之前所說的一個(gè)服務(wù)器有多個(gè)網(wǎng)卡,這時(shí)候可能就會(huì)有多個(gè)地址。這些地址就會(huì)用ai_next指針串聯(lián)起來,形成一個(gè)單鏈表。

然后我們拿到這個(gè)地址鏈表,去遍歷它,對(duì)應(yīng)取出IPV4、IPV6的地址,封裝成NSData并裝到數(shù)組中去。

  1. 如果中間有錯(cuò)誤,賦值錯(cuò)誤,返回地址數(shù)組,理清楚這幾個(gè)結(jié)構(gòu)體與函數(shù),這個(gè)方法還是相當(dāng)容易讀的,具體的細(xì)節(jié)可以看看注釋。

接著我們回到本文方法二,就要用這個(gè)地址數(shù)組去做連接了。

//異步去發(fā)起連接
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
     
     [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
這里調(diào)用了我們本文方法六--開始連接的方法1
//連接的最終方法 1
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
     LogTrace();
     
     NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    //至少有一個(gè)server地址
     NSAssert(address4 || address6, @"Expected at least one valid address");
     
    //如果狀態(tài)不一致,說明斷開連接
     if (aStateIndex != stateIndex)
     {
          LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
          
          // The connect operation has been cancelled.
          // That is, socket was disconnected, or connection has already timed out.
          return;
     }
     
     // Check for problems
     //分開判斷。
     BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
     BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
     
     if (isIPv4Disabled && (address6 == nil))
     {
          NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
          
          [self closeWithError:[self otherError:msg]];
          return;
     }
     
     if (isIPv6Disabled && (address4 == nil))
     {
          NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
          
          [self closeWithError:[self otherError:msg]];
          return;
     }
     
     // Start the normal connection process
     
     NSError *err = nil;
    //調(diào)用連接方法,如果失敗,則錯(cuò)誤返回
     if (![self connectWithAddress4:address4 address6:address6 error:&err])
     {
          [self closeWithError:err];
     }
}

這個(gè)方法也比較簡(jiǎn)單,基本上就是做了一些錯(cuò)誤的判斷。比如:

  1. 判斷在不在這個(gè)socket隊(duì)列。
  2. 判斷傳過來的aStateIndex和屬性stateIndex是不是同一個(gè)值。說到這個(gè)值,不得不提的是大神用的框架,在容錯(cuò)處理上,做的真不是一般的嚴(yán)謹(jǐn)。從這個(gè)stateIndex上就能略見一二。
    這個(gè)aStateIndex是我們之前調(diào)用方法,用屬性傳過來的,所以按道理說,是肯定一樣的。但是就怕在調(diào)用過程中,這個(gè)值發(fā)生了改變,這時(shí)候整個(gè)socket配置也就完全不一樣了,有可能我們已經(jīng)置空地址、銷毀socket、斷開連接等等...等我們后面再來看這個(gè)屬性stateIndex在什么地方會(huì)發(fā)生改變。
  3. 判斷config中是需要哪種配置,它的參數(shù)對(duì)應(yīng)了一個(gè)枚舉:
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個(gè)大家很好理解,無非就是用IPV4還是IPV6。
而第4個(gè)官方注釋意思是,我們即使關(guān)閉讀的流,也會(huì)保持Socket開啟。至于具體是什么意思,我們先不在這里討論,等后文再說。

這里調(diào)用了我們本文方法七--開始連接的方法2
//連接最終方法 2。用兩個(gè)Server地址去連接,失敗返回NO,并填充error
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
    LogTrace();
    
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    
    //輸出一些東西?
    LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
    LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
    
    // Determine socket type
    
    //判斷是否傾向于IPV6
    BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
    
    // Create and bind the sockets
    
    //如果有IPV4地址,創(chuàng)建IPV4 Socket
    if (address4)
    {
        LogVerbose(@"Creating IPv4 socket");
        
        socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
    }
    //如果有IPV6地址,創(chuàng)建IPV6 Socket
    if (address6)
    {
        LogVerbose(@"Creating IPv6 socket");
        
        socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
    }
    
    //如果都為空,直接返回
    if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
    {
        return NO;
    }
    
    //主選socketFD,備選alternateSocketFD
    int socketFD, alternateSocketFD;
    //主選地址和備選地址
    NSData *address, *alternateAddress;
    
    //IPV6
    if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
    {
        socketFD = socket6FD;
        alternateSocketFD = socket4FD;
        address = address6;
        alternateAddress = address4;
    }
    //主選IPV4
    else
    {
        socketFD = socket4FD;
        alternateSocketFD = socket6FD;
        address = address4;
        alternateAddress = address6;
    }
    //拿到當(dāng)前狀態(tài)
    int aStateIndex = stateIndex;
    //用socket和address去連接
    [self connectSocket:socketFD address:address stateIndex:aStateIndex];
    
    //如果有備選地址
    if (alternateAddress)
    {
        //延遲去連接備選的地址
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
            [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
        });
    }
    
    return YES;
}

這個(gè)方法也僅僅是連接中過渡的一個(gè)方法,做的事也非常簡(jiǎn)單:

  1. 就是拿到IPV4和IPV6地址,先去創(chuàng)建對(duì)應(yīng)的socket,注意這個(gè)socket是本機(jī)客戶端的,和server端沒有關(guān)系。這里服務(wù)端的IPV4和IPV6地址僅僅是用來判斷是否需要去創(chuàng)建對(duì)應(yīng)的本機(jī)Socket。這里去創(chuàng)建socket會(huì)帶上我們之前生成的本地地址信息connectInterface4或者connectInterface6
  2. 根據(jù)我們的config配置,得到主選連接和備選連接。 然后先去連接主選連接地址,在用我們一開始初始化中設(shè)置的屬性alternateAddressDelay,就是這個(gè)備選連接延時(shí)的屬性,去延時(shí)連接備選地址(當(dāng)然如果主選地址在此時(shí)已經(jīng)連接成功,會(huì)再次連接導(dǎo)致socket錯(cuò)誤,并且關(guān)閉)。

這兩步分別調(diào)用了各自的方法去實(shí)現(xiàn),接下來我們先來看創(chuàng)建本機(jī)Socket的方法:

本文方法八--創(chuàng)建Socket:
//創(chuàng)建Socket
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
    //創(chuàng)建socket,用的SOCK_STREAM TCP流
    int socketFD = socket(family, SOCK_STREAM, 0);
    //如果創(chuàng)建失敗
    if (socketFD == SOCKET_NULL)
    {
        if (errPtr)
            *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
        
        return socketFD;
    }
    
    //和connectInterface綁定
    if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
    {
        //綁定失敗,直接關(guān)閉返回
        [self closeSocket:socketFD];
        
        return SOCKET_NULL;
    }
    
    // Prevent SIGPIPE signals
    //防止終止進(jìn)程的信號(hào)?
    int nosigpipe = 1;
    //SO_NOSIGPIPE是為了避免網(wǎng)絡(luò)錯(cuò)誤,而導(dǎo)致進(jìn)程退出。用這個(gè)來避免系統(tǒng)發(fā)送signal
    setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
    
    return socketFD;
}

這個(gè)方法做了這么幾件事:

  1. 創(chuàng)建了一個(gè)socket:
 //創(chuàng)建一個(gè)socket,返回值為Int。(注scoket其實(shí)就是Int類型)
    //第一個(gè)參數(shù)addressFamily IPv4(AF_INET) 或 IPv6(AF_INET6)。
    //第二個(gè)參數(shù) type 表示 socket 的類型,通常是流stream(SOCK_STREAM) 或數(shù)據(jù)報(bào)文datagram(SOCK_DGRAM)
    //第三個(gè)參數(shù) protocol 參數(shù)通常設(shè)置為0,以便讓系統(tǒng)自動(dòng)為選擇我們合適的協(xié)議,對(duì)于 stream socket 來說會(huì)是 TCP 協(xié)議(IPPROTO_TCP),而對(duì)于 datagram來說會(huì)是 UDP 協(xié)議(IPPROTO_UDP)。
int socketFD = socket(family, SOCK_STREAM, 0);

其實(shí)這個(gè)函數(shù)在之前那篇IM文章中也講過了,大家參考參考注釋看看就可以了,這里如果返回值為-1,說明創(chuàng)建失敗。

  1. 去綁定我們之前創(chuàng)建的本地地址,它調(diào)用了另外一個(gè)方法來實(shí)現(xiàn)。
  2. 最后我們調(diào)用了如下函數(shù)?:
   setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));

那么這個(gè)函數(shù)是做什么用的呢?簡(jiǎn)單來說,它就是給我們的socket加一些額外的設(shè)置項(xiàng),來配置socket的一些行為。它還有許多的用法,具體可以參考這篇文章:setsockopt函數(shù)

而這里的目的是為了來避免網(wǎng)絡(luò)錯(cuò)誤而出現(xiàn)的進(jìn)程退出的情況,調(diào)用了這行函數(shù),網(wǎng)絡(luò)錯(cuò)誤后,系統(tǒng)不再發(fā)送進(jìn)程退出的信號(hào)。
關(guān)于這個(gè)進(jìn)程退出的錯(cuò)誤可以參考這篇文章:Mac OSX下SO_NOSIGPIPE的怪異表現(xiàn)

未完總結(jié):

connect篇還沒有完結(jié),奈何篇幅問題,只能斷在這里。下一個(gè)方法將是socket本地綁定的方法。再下面就是我們最終的連接方法了,歷經(jīng)九九八十一難,馬上就要取到真經(jīng)了...(然而這僅僅是一個(gè)開始...)
下一篇將會(huì)承接這一篇的內(nèi)容繼續(xù)講,包括最終連接、連接完成后的source和流的處理。
我們還會(huì)去講講iOS作為服務(wù)端的accpet建立連接的流程。
除此之外還有 unix domin socket(進(jìn)程間通信)的連接。

最近總感覺很浮躁,貼一句一直都很喜歡的話:
上善若水。水善利萬物而不爭(zhēng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容