iOS GCDAsyncSocket源碼分析(一)

序言

上一篇文章文章中酬滤,簡單介紹了GCDAsyncSocket的使用签餐,socket創(chuàng)建、連接盯串、發(fā)送消息氯檐、接收消息、關(guān)閉socket体捏、粘包分包冠摄、以及心跳包機(jī)制。并且立下了一個(gè)flag几缭,所以在這篇文章河泳,將帶來GCDAsyncSocket的源碼分析,看看在GCDAsyncSocket中是如何運(yùn)用原生代碼并封裝起來的年栓,在簡單實(shí)現(xiàn)的原生代碼基礎(chǔ)上拆挥,他又做了什么樣的操作。

我們還是按照創(chuàng)建socket某抓、連接socket纸兔、發(fā)送消息黄锤、接收消息、關(guān)閉socket的順序食拜,一步一步深入了解GCDAsyncSocket。

1. GCDAsyncSocket初始化

在開始之前副编,GCDAsyncSocket.m中聲明了許許多多的成員變量负甸,先看看都是啥。

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

創(chuàng)建函數(shù)

self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

這個(gè)init方法最終將會(huì)來到蚕捉,在這個(gè)方法里,socketQueue傳值為NULL柴淘,所以后面如果有sq的部分可以先行跳過迫淹,等梳理完了,再去看看這個(gè)sq具體都干了啥为严。

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
    if((self = [super init]))
    {
        delegate = aDelegate;
        delegateQueue = dq;
        
         //這個(gè)宏是在sdk6.0之后才有的,如果是之前的,則OS_OBJECT_USE_OBJC為0敛熬,!0即執(zhí)行if語句
        //對(duì)6.0的適配,如果是6.0以下第股,則去retain release应民,6.0之后ARC也管理了GCD
        //作者很細(xì)
        #if !OS_OBJECT_USE_OBJC
        
        if (dq) dispatch_retain(dq);
        #endif
        
        //創(chuàng)建socket,先都置為 -1 , 代表socket默認(rèn)創(chuàng)建失敗
        //本機(jī)的ipv4
        socket4FD = SOCKET_NULL;
        //ipv6
        socket6FD = SOCKET_NULL;
        //應(yīng)該是UnixSocket
        socketUN = SOCKET_NULL;
        //url
        socketUrl = nil;
        //狀態(tài)
        stateIndex = 0;
        //這里并沒有sq夕吻,可以選擇跳過
        if (sq)
        {
            //如果scoketQueue是global的,則報(bào)錯(cuò)诲锹。斷言必須要一個(gè)非并行queue。
            NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
                     @"The given socketQueue parameter must not be a concurrent queue.");
            NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
                     @"The given socketQueue parameter must not be a concurrent queue.");
            NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                     @"The given socketQueue parameter must not be a concurrent queue.");
            //拿到scoketQueue
            socketQueue = sq;
            //iOS6之下retain
            #if !OS_OBJECT_USE_OBJC
            dispatch_retain(sq);
            #endif
        }
        else
        {
            //沒有的話創(chuàng)建一個(gè)socketQueue涉馅,  名字為:GCDAsyncSocket,NULL = 串行
            socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
        }
        
        //比如原來為   0X123 -> NULL 變成  0X222->0X123->NULL
        //自己的指針等于自己原來的指針归园,成二級(jí)指針了  看了注釋是為了以后省略&,讓代碼更可讀?
        //這里不懂作者的用意稚矿,繼續(xù)往下看
        IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
        
        
        void *nonNullUnusedPointer = (__bridge void *)self;
        
        //dispatch_queue_set_specific給當(dāng)前隊(duì)里加一個(gè)標(biāo)識(shí) dispatch_get_specific當(dāng)前線程取出這個(gè)標(biāo)識(shí)蔓倍,判斷是不是在這個(gè)隊(duì)列
        dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
        //讀的數(shù)組 
        readQueue = [[NSMutableArray alloc] initWithCapacity:5];
        currentRead = nil;
        
        //寫的數(shù)組
        writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
        currentWrite = nil;
        
        //緩沖區(qū) 設(shè)置大小為 4kb
        preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
    
#pragma mark alternateAddressDelay??
        //交替地址延時(shí)?盐捷? wtf 應(yīng)該是用來給備用地址的
        alternateAddressDelay = 0.3;
    }
    return self;
}

看完這段代碼...懵逼偶翅。只是一些初始化操作。本來還以為create()會(huì)在這里面呢碉渡,很無奈啊聚谁,哎,先不管了滞诺,繼續(xù)往下看吧形导。

2. GCDAsyncSocket Connect

外層調(diào)用

[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];

底層最終會(huì)來到這里环疼,每個(gè)方法都好長啊 - - 。這里的inInterface傳入的是nil朵耕,所以炫隶,跟上面那個(gè)方法的sq一樣,如果有遇到可以選擇跳過阎曹。

- (BOOL)connectToHost:(NSString *)inHost
               onPort:(uint16_t)port
         viaInterface:(NSString *)inInterface
          withTimeout:(NSTimeInterval)timeout
                error:(NSError **)errPtr
{
    //LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -- 跟蹤當(dāng)前行為
    LogTrace();
    
    //拿到host ,copy防止值被修改
    NSString *host = [inHost copy];
    //interface伪阶?接口?先不管 反正是nil
    NSString *interface = [inInterface copy];
    
    //聲明兩個(gè)__block的臨時(shí)變量
    __block BOOL result = NO;
    //error信息
    __block NSError *preConnectErr = nil;
    
    //gcdBlock ,都包裹在自動(dòng)釋放池中 :
    // 1: 大量臨時(shí)變量 connect : 重連
    // 2: 自定義線程管理 : nsoperation
    // 3: 非UI 命令 工具
    dispatch_block_t block = ^{ @autoreleasepool {
        
        // Check for problems with host parameter
        // 翻譯:檢查host參數(shù) 是否存在問題
        if ([host length] == 0)
        {
            NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
            preConnectErr = [self badParamError:msg];
            
            // 其實(shí)就是return,大牛的代碼真是充滿逼格 - ret
            // 里面有注釋处嫌,有想法的可以自己去看看栅贴,大概意思就是
            // 可以讓這個(gè)return能更快的被read,后面還有很多地方被調(diào)用到
            return_from_block;
        }
        
        //一個(gè)前置的檢查,如果沒通過返回熏迹,這q個(gè)檢查里檐薯,如果interface有值,則會(huì)將本機(jī)的IPV4 IPV6的 address設(shè)置上注暗。
        // 參數(shù) : 指針 操作同一片內(nèi)存空間
        // 因?yàn)閕nterface 是nil坛缕,所以不會(huì)執(zhí)行return
        if (![self preConnectWithInterface:interface error:&preConnectErr])
        {
            return_from_block;
        }

        // We've made it past all the checks.我們已經(jīng)檢查了所有參數(shù)
        // It's time to start the connection process.是時(shí)候開始連接了
        //flags 做或等運(yùn)算。 flags標(biāo)識(shí)為開始Socket連接
        flags |= kSocketStarted;
        
        //又是一個(gè){}? 只是為了標(biāo)記么捆昏?
        LogVerbose(@"Dispatching DNS lookup...");
        
        //很可能給我們的服務(wù)端的參數(shù)是一個(gè)可變字符串
        //所以我們需要copy祷膳,在Block里同步的執(zhí)行
        //這種基于Block的異步查找,不需要擔(dān)心它被改變
        //copy屡立,防止改變
        NSString *hostCpy = [host copy];
        
        //拿到狀態(tài) 初始化的時(shí)候 stateIndex = 0
        int aStateIndex = stateIndex;
        __weak GCDAsyncSocket *weakSelf = self;
        
        //獲取全局并發(fā)Queue 
        dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //異步執(zhí)行直晨,這里的autoreleasepool 跟上面的一樣,可以往上翻
        dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
            //忽視循環(huán)引用膨俐,牛逼
        #pragma clang diagnostic push
        #pragma clang diagnostic warning "-Wimplicit-retain-self"
            
            //查找錯(cuò)誤
            NSError *lookupErr = nil;
            //server地址數(shù)組(包含IPV4 IPV6的地址  sockaddr_in6勇皇、sockaddr_in類型)
            NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
            
            //strongSelf
            __strong GCDAsyncSocket *strongSelf = weakSelf;
            
            //完整Block安全形態(tài),在加個(gè)if
            if (strongSelf == nil) return_from_block;
            
            //如果有錯(cuò)
            if (lookupErr)
            {
                //用cocketQueue
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    //一些錯(cuò)誤處理焚刺,清空一些數(shù)據(jù)等等
                    [strongSelf lookup:aStateIndex didFail:lookupErr];
                }});
            }
            //正常
            else
            {
                
                NSData *address4 = nil;
                NSData *address6 = nil;
                //遍歷地址數(shù)組
                for (NSData *address in addresses)
                {
                    //判斷address4為空敛摘,且address為IPV4
                    if (!address4 && [[self class] isIPv4Address:address])
                    {
                        address4 = address;
                    }
                    //判斷address6為空,且address為IPV6
                    else if (!address6 && [[self class] isIPv6Address:address])
                    {
                        address6 = address;
                    }
                }
                //異步去發(fā)起
                dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                    // 方法名大概是說乳愉,address4 address6 兩個(gè)地址都成功獲取到了兄淫。
                    [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
                }});
            }
            
        #pragma clang diagnostic pop
        }});
        
        
        //開啟連接超時(shí)
        [self startConnectTimeout:timeout];
        
        result = YES;
    }};
    //在socketQueue中執(zhí)行這個(gè)Block
    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
        block();
    //否則同步的調(diào)起這個(gè)queue去執(zhí)行
    else
        dispatch_sync(socketQueue, block);
    
    //如果有錯(cuò)誤,賦值錯(cuò)誤
    if (errPtr) *errPtr = preConnectErr;
    //把連接是否成功的result返回
    return result;
}

這個(gè)connect跟想的也不太一樣蔓姚,并沒有熟悉的connect()捕虽,有毒。但是坡脐!還知道這個(gè)方法里都干了啥呢泄私。

[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];

一探究竟!

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

咦咧纠,好像有點(diǎn)苗頭蓬痒,看作者悄咪咪的都干了些啥。

    if (![self connectWithAddress4:address4 address6:address6 error:&err])

繼續(xù)點(diǎn)進(jìn)去看看

- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
    LogTrace();
    
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
    
    //輸出了兩個(gè)地址的信息
    LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
    LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
    
    //判斷是否傾向于IPV6
    BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
    
    // Create and bind the sockets
    
    //如果有IPV4地址
    if (address4)
    {
        LogVerbose(@"Creating IPv4 socket");
        // 咦漆羔?這不是創(chuàng)建嗎梧奢,瞧瞧我發(fā)現(xiàn)了啥。
        socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
    }
    //如果有IPV6地址钧椰,同上
    if (address6)
    {
        LogVerbose(@"Creating IPv6 socket");
        
        socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
    }
    
    //如果都為空,直接返回
    if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
    {
        return NO;
    }
    
    //主選socketFD,備選alternateSocketFD
    int socketFD, alternateSocketFD;
    //主選地址和備選地址
    NSData *address, *alternateAddress;
    
    //IPV6
    if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
    {
        socketFD = socket6FD;
        alternateSocketFD = socket4FD;
        address = address6;
        alternateAddress = address4;
    }
    //主選IPV4
    else
    {
        socketFD = socket4FD;
        alternateSocketFD = socket6FD;
        address = address4;
        alternateAddress = address6;
    }
    //拿到當(dāng)前狀態(tài)
    int aStateIndex = stateIndex;

    // 我去符欠,這不是連接嗎嫡霞?都悄咪咪的把創(chuàng)建跟連接放在這個(gè)方法里了,糟老頭子壞得很希柿。
    [self connectSocket:socketFD address:address stateIndex:aStateIndex];
    
    //如果有備選地址
    if (alternateAddress)
    {
        //延遲去連接備選的地址
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
            [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
        });
    }
    
    return YES;
}

作者是真的皮啊诊沪,把這么重要的方法,放在一個(gè)if里面曾撤?騷還是你騷啊端姚。
總算是找到創(chuàng)建跟連接了,說什么也要點(diǎn)進(jìn)去看看吧挤悉。
先看創(chuàng)建

//創(chuàng)建Socket
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
    // 注意
    // 這個(gè)connectInterface 創(chuàng)建socketFD4跟6時(shí)渐裸,分別是傳入了connectInterface4與connectInterface6
    // 這兩個(gè)值,在preConnectWithInterface時(shí)装悲,如果interface不為空昏鹃,就會(huì)賦值,但是interface一直是nil,所以
    // connectInterface4與connectInterface6 都是nil
    
    
    // 創(chuàng)建socket,用的SOCK_STREAM TCP流
    // 總算是看到了熟悉的東西
    int socketFD = socket(family, SOCK_STREAM, 0);
    //如果創(chuàng)建失敗 SOCKET_NULL = -1
    if (socketFD == SOCKET_NULL)
    {
        if (errPtr)
            *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
        
        return socketFD;
    }
    
    //和connectInterface綁定诀诊,由于connectInterface 是nil 所以這個(gè)方法會(huì)放回YES,
    //所以不會(huì)走進(jìn)去
    if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
    {
        //綁定失敗洞渤,直接關(guān)閉返回
        [self closeSocket:socketFD];
        
        return SOCKET_NULL;
    }
    
    // Prevent SIGPIPE signals
    //防止終止進(jìn)程的信號(hào)?
    int nosigpipe = 1;
    //SO_NOSIGPIPE是為了避免網(wǎng)絡(luò)錯(cuò)誤属瓣,而導(dǎo)致進(jìn)程退出载迄。用這個(gè)來避免系統(tǒng)發(fā)送signal
    //setsockopt()函數(shù),用于任意類型抡蛙、任意狀態(tài)套接口的設(shè)置選項(xiàng)值护昧。百度百科有詳解
    setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
    
    return socketFD;
}

再來就是連接socket

- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
    //已連接,關(guān)閉連接返回
    if (self.isConnected)
    {
        [self closeSocket:socketFD];
        return;
    }
    
    // Start the connection process in a background queue
    //開始連接過程粗截,在后臺(tái)queue中
    __weak GCDAsyncSocket *weakSelf = self;
    
    //獲取到全局Queue
    dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //新線程
    dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
        //調(diào)用connect方法捏卓,該函數(shù)阻塞線程,所以要異步新線程
        //客戶端向特定網(wǎng)絡(luò)地址的服務(wù)器發(fā)送連接請(qǐng)求,連接成功返回0怠晴,失敗返回 -1遥金。
        int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
        
        //老樣子,安全判斷
        __strong GCDAsyncSocket *strongSelf = weakSelf;
        if (strongSelf == nil) return_from_block;
        
        //在socketQueue中蒜田,開辟線程
        dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
            //如果狀態(tài)為已經(jīng)連接稿械,關(guān)閉連接返回
            if (strongSelf.isConnected)
            {
                [strongSelf closeSocket:socketFD];
                // 又是這個(gè)裝逼寫法
                return_from_block;
            }
            
            //說明連接成功
            if (result == 0)
            {
                //關(guān)閉掉另一個(gè)沒用的socket
                [self closeUnusedSocket:socketFD];
                //調(diào)用didConnect,生成stream冲粤,改變狀態(tài)等等美莫!
                [strongSelf didConnect:aStateIndex];
            }
            //連接失敗
            else
            {
                //關(guān)閉當(dāng)前socket
                [strongSelf closeSocket:socketFD];
                
                // If there are no more sockets trying to connect, we inform the error to the delegate
                //返回連接錯(cuò)誤的error
                if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
                {
                    NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
                    [strongSelf didNotConnect:aStateIndex error:error];
                }
            }
        }});
        
#pragma clang diagnostic pop
    });
    //輸出正在連接中
    LogVerbose(@"Connecting...");
}

至此,我們就看到了socket的創(chuàng)建跟連接的實(shí)現(xiàn)原理梯捕,接下來說讀寫操作厢呵。
由于篇幅問題這里另起一篇文章看這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市傀顾,隨后出現(xiàn)的幾起案子襟铭,更是在濱河造成了極大的恐慌,老刑警劉巖短曾,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寒砖,死亡現(xiàn)場離奇詭異,居然都是意外死亡嫉拐,警方通過查閱死者的電腦和手機(jī)哩都,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婉徘,“玉大人漠嵌,你說我怎么就攤上這事「呛簦” “怎么了献雅?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塌计。 經(jīng)常有香客問我挺身,道長,這世上最難降的妖魔是什么锌仅? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任章钾,我火速辦了婚禮,結(jié)果婚禮上热芹,老公的妹妹穿的比我還像新娘贱傀。我一直安慰自己,他們只是感情好伊脓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布府寒。 她就那樣靜靜地躺著魁衙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪株搔。 梳的紋絲不亂的頭發(fā)上剖淀,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音纤房,去河邊找鬼纵隔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛炮姨,可吹牛的內(nèi)容都是我干的捌刮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼舒岸,長吁一口氣:“原來是場噩夢啊……” “哼绅作!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛾派,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤俄认,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后碍脏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梭依,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稍算,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年典尾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糊探。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钾埂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出科平,到底是詐尸還是另有隱情褥紫,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布瞪慧,位于F島的核電站髓考,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弃酌。R本人自食惡果不足惜氨菇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妓湘。 院中可真熱鬧查蓉,春花似錦、人聲如沸榜贴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹃共,卻和暖如春鬼佣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背及汉。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工沮趣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坷随。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓房铭,卻偏偏與公主長得像温眉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子类溢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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