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

前言:

本文為CocoaAsyncSocket Read篇終,將重點(diǎn)涉及該框架是如何利用緩沖區(qū)對數(shù)據(jù)進(jìn)行讀取、以及各種情況下的數(shù)據(jù)包處理惰帽,其中還包括普通的、和基于TLS的不同讀取操作等等父虑。
注:由于該框架源碼篇幅過大该酗,且有大部分相對抽象的數(shù)據(jù)操作邏輯,盡管樓主竭力想要簡單的去陳述相關(guān)內(nèi)容士嚎,但是閱讀起來仍會(huì)有一定的難度垂涯。如果不是誠心想學(xué)習(xí)IM相關(guān)知識烁焙,在這里就可以離場了...

本文系列第一篇:Connect篇已經(jīng)完結(jié),感興趣可以看看:
iOS即時(shí)通訊進(jìn)階 - CocoaAsyncSocket源碼解析(Connect篇)
iOS即時(shí)通訊進(jìn)階 - CocoaAsyncSocket源碼解析(Connect篇終)

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

注:文中涉及代碼比較多耕赘,建議大家結(jié)合源碼一起閱讀比較容易能加深理解。這里有樓主標(biāo)注好注釋的源碼膳殷,有需要的可以作為參照:CocoaAsyncSocket源碼注釋
如果對該框架用法不熟悉的話操骡,可以參考樓主之前文章:
iOS即時(shí)通訊,從入門到“放棄”赚窃?册招,
即時(shí)通訊下數(shù)據(jù)粘包、斷包處理實(shí)例(基于CocoaAsyncSocket)
或者自行查閱勒极。

目錄:
  • 1.淺析Read讀取是掰,并闡述數(shù)據(jù)從socket到用戶手中的流程。?
  • 2.講講兩種TLS建立連接的過程辱匿。?
  • 3.深入講解Read的核心方法---doReadData的實(shí)現(xiàn)键痛。?
正文:

前文講完了兩次TLS建立連接的流程,接著就是本篇的重頭戲了:doReadData方法匾七。在這里我不準(zhǔn)備直接把這個(gè)整個(gè)方法列出來絮短,因?yàn)榫凸膺@一個(gè)方法,加上注釋有1200行昨忆,整個(gè)貼過來也無法展開描述丁频,所以在這里我打算對它分段進(jìn)行講解:

注:以下代碼整個(gè)包括在doReadData大括號中:

//讀取數(shù)據(jù)
- (void)doReadData
{
  ....
}

Part1.無法正常讀取數(shù)據(jù)時(shí)的前置處理:
//如果當(dāng)前讀取的包為空,或者flag為讀取停止,這兩種情況是不能去讀取數(shù)據(jù)的
if ((currentRead == nil) || (flags & kReadsPaused))
{
     LogVerbose(@"No currentRead or kReadsPaused");
     
     // Unable to read at this time
     //如果是安全的通信邑贴,通過TLS/SSL
     if (flags & kSocketSecure)
     {
       //刷新SSLBuffer,把數(shù)據(jù)從鏈路上移到prebuffer中 (當(dāng)前不讀取數(shù)據(jù)的時(shí)候做)
          [self flushSSLBuffers];
     }
     
   //判斷是否用的是 CFStream的TLS
     if ([self usingCFStreamForTLS])
     {

     }
     else
     {
       //掛起source
          if (socketFDBytesAvailable > 0)
          {
               [self suspendReadSource];
          }
     }
     return;
}

當(dāng)我們當(dāng)前讀取的包是空或者標(biāo)記為讀停止?fàn)顟B(tài)的時(shí)候席里,則不會(huì)去讀取數(shù)據(jù)。
前者不難理解拢驾,因?yàn)槲覀円x取的數(shù)據(jù)最終是要傳給currentRead中去的奖磁,所以如果currentRead為空,我們?nèi)プx數(shù)據(jù)也沒有意義独旷。
后者kReadsPaused標(biāo)記是從哪里加上的呢署穗?我們?nèi)炙阉饕幌拢l(fā)現(xiàn)它才read超時(shí)的時(shí)候被添加嵌洼。
講到這我們順便來看這個(gè)讀取超時(shí)的一個(gè)邏輯案疲,我們每次做讀取任務(wù)傳進(jìn)來的超時(shí),都會(huì)調(diào)用這么一個(gè)方法:

Part2.讀取超時(shí)處理:
[self setupReadTimerWithTimeout:currentRead->timeout];
//初始化讀的超時(shí)
- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
{
    if (timeout >= 0.0)
    {
        //生成一個(gè)定時(shí)器source
        readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
        
        __weak GCDAsyncSocket *weakSelf = self;
        
        //句柄
        dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
        #pragma clang diagnostic push
        #pragma clang diagnostic warning "-Wimplicit-retain-self"
            
            __strong GCDAsyncSocket *strongSelf = weakSelf;
            if (strongSelf == nil) return_from_block;
            
            //執(zhí)行超時(shí)操作
            [strongSelf doReadTimeout];
            
        #pragma clang diagnostic pop
        }});
        
        #if !OS_OBJECT_USE_OBJC
        dispatch_source_t theReadTimer = readTimer;
        
        //取消的句柄
        dispatch_source_set_cancel_handler(readTimer, ^{
        #pragma clang diagnostic push
        #pragma clang diagnostic warning "-Wimplicit-retain-self"
            
            LogVerbose(@"dispatch_release(readTimer)");
            dispatch_release(theReadTimer);
            
        #pragma clang diagnostic pop
        });
        #endif
        
        //定時(shí)器延時(shí) timeout時(shí)間執(zhí)行
        dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
        //間隔為永遠(yuǎn)麻养,即只執(zhí)行一次
        dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
        dispatch_resume(readTimer);
    }
}

這個(gè)方法定義了一個(gè)GCD定時(shí)器褐啡,這個(gè)定時(shí)器只執(zhí)行一次,間隔就是我們的超時(shí)鳖昌,很顯然這是一個(gè)延時(shí)執(zhí)行备畦,那小伙伴要問了低飒,這里為什么我們不用NSTimer或者下面這種方式:

[self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>

原因很簡單,performSelector是基于runloop才能使用的懂盐,它本質(zhì)是轉(zhuǎn)化成runloop基于非端口的源source0褥赊。很顯然我們所在的socketQueue開辟出來的線程,并沒有添加一個(gè)runloop莉恼。而NSTimer也是一樣拌喉。

所以這里我們用GCD Timer,因?yàn)樗腔?code>XNU內(nèi)核來實(shí)現(xiàn)的俐银,并不需要借助于runloop尿背。

這里當(dāng)超時(shí)時(shí)間間隔到達(dá)時(shí),我們會(huì)執(zhí)行超時(shí)操作:

[strongSelf doReadTimeout];
//執(zhí)行超時(shí)操作
- (void)doReadTimeout
{
    // This is a little bit tricky.
    // Ideally we'd like to synchronously query the delegate about a timeout extension.
    // But if we do so synchronously we risk a possible deadlock.
    // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
    
    //因?yàn)檫@里用同步容易死鎖捶惜,所以用異步從代理中回調(diào)
    
    //標(biāo)記讀暫停
    flags |= kReadsPaused;
    
    __strong id theDelegate = delegate;

    //判斷是否實(shí)現(xiàn)了延時(shí)  補(bǔ)時(shí)的代理
    if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
    {
        //拿到當(dāng)前讀的包
        GCDAsyncReadPacket *theRead = currentRead;
        
        //代理queue中回調(diào)
        dispatch_async(delegateQueue, ^{ @autoreleasepool {
            
            NSTimeInterval timeoutExtension = 0.0;
            
            //調(diào)用代理方法田藐,拿到續(xù)的時(shí)長
            timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
                                                                         elapsed:theRead->timeout
                                                                       bytesDone:theRead->bytesDone];
            
            //socketQueue中,做延時(shí)
            dispatch_async(socketQueue, ^{ @autoreleasepool {
                
                [self doReadTimeoutWithExtension:timeoutExtension];
            }});
        }});
    }
    else
    {
        [self doReadTimeoutWithExtension:0.0];
    }
}
//做讀取數(shù)據(jù)延時(shí)
- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
    if (currentRead)
    {
        if (timeoutExtension > 0.0)
        {
            //把超時(shí)加上
            currentRead->timeout += timeoutExtension;
            
            // Reschedule the timer
            //重新生成時(shí)間
            dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
            //重置timer時(shí)間
            dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
            
            // Unpause reads, and continue
            //在把paused標(biāo)記移除
            flags &= ~kReadsPaused;
            //繼續(xù)去讀取數(shù)據(jù)
            [self doReadData];
        }
        else
        {
            //輸出讀取超時(shí)吱七,并斷開連接
            LogVerbose(@"ReadTimeout");
            
            [self closeWithError:[self readTimeoutError]];
        }
    }
}

這里調(diào)用了續(xù)時(shí)代理汽久,如果我們實(shí)現(xiàn)了這個(gè)代理,則可以增加這個(gè)超時(shí)時(shí)間陪捷,然后重新生成超時(shí)定時(shí)器回窘,移除讀取停止的標(biāo)記kReadsPaused。繼續(xù)去讀取數(shù)據(jù)市袖。
否則我們就斷開socket啡直。
注意:這個(gè)定時(shí)器會(huì)被取消,如果當(dāng)前數(shù)據(jù)包被讀取完成苍碟,這樣就不會(huì)走到定時(shí)器超時(shí)的時(shí)間酒觅,則不會(huì)斷開socket。講到這是不是大家就有印象了微峰?這個(gè)就是之前在樓主:
iOS即時(shí)通訊舷丹,從入門到“放棄”?中講過的可以被用來做PingPong機(jī)制的原理蜓肆。

我們接著回到doReadData中颜凯,我們講到如果當(dāng)前讀取包為空或者狀態(tài)為kReadsPaused,我們就去執(zhí)行一些非讀取數(shù)據(jù)的處理仗扬。
這里我們第一步去判斷當(dāng)前連接是否為kSocketSecure症概,也就是安全通道的TLS。如果是我們則調(diào)用:

if (flags & kSocketSecure)
{
     //刷新,把TLS加密型的數(shù)據(jù)從鏈路上移到prebuffer中 (當(dāng)前暫停的時(shí)候做)
     [self flushSSLBuffers];
}

按理說早芭,我們有當(dāng)前讀取包的時(shí)候彼城,在去從prebuffersocket中去讀取,但是這里為什么要提前去讀呢募壕?
我們來看看這個(gè)框架作者的解釋:

// Here's the situation:
// We have an established secure connection.
// There may not be a currentRead, but there might be encrypted data sitting around for us.
// When the user does get around to issuing a read, that encrypted data will need to be decrypted.
// So why make the user wait?
// We might as well get a head start on decrypting some data now.
// The other reason we do this has to do with detecting a socket disconnection.
// The SSL/TLS protocol has it's own disconnection handshake.
// So when a secure socket is closed, a "goodbye" packet comes across the wire.
// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.

簡單來講调炬,就是我們用TLS類型的Socket,讀取數(shù)據(jù)的時(shí)候需要解密的過程舱馅,而這個(gè)過程是費(fèi)時(shí)的缰泡,我們沒必要讓用戶在讀取數(shù)據(jù)的時(shí)候去等待這個(gè)解密的過程,我們可以提前在數(shù)據(jù)一到達(dá)代嗤,就去讀取解密匀谣。
而且這種方式,還能時(shí)刻根據(jù)TLSgoodbye包來準(zhǔn)確的檢測到TCP斷開連接资溃。

在我們來看flushSSLBuffers方法之前,我們先來看看這個(gè)一直提到的全局緩沖區(qū)prebuffer的定義烈炭,它其實(shí)就是下面這么一個(gè)類的實(shí)例:

Part3.GCDAsyncSocketPreBuffer的定義
@interface GCDAsyncSocketPreBuffer : NSObject
{
    //unsigned char
    //提前的指針溶锭,指向這塊提前的緩沖區(qū)
    uint8_t *preBuffer;
    //size_t 它是一個(gè)與機(jī)器相關(guān)的unsigned類型,其大小足以保證存儲(chǔ)內(nèi)存中對象的大小符隙。
    //它可以存儲(chǔ)在理論上是可能的任何類型的數(shù)組的最大大小
    size_t preBufferSize;
    //讀的指針
    uint8_t *readPointer;
    //寫的指針
    uint8_t *writePointer;
}

里面存了3個(gè)指針趴捅,包括preBuffer起點(diǎn)指針、當(dāng)前讀寫所處位置指針霹疫、以及一個(gè)preBufferSize拱绑,這個(gè)sizepreBuffer所指向的位置,在內(nèi)存中分配的空間大小丽蝎。

我們來看看它的幾個(gè)方法:

//初始化
- (id)initWithCapacity:(size_t)numBytes
{
    if ((self = [super init]))
    {
        //設(shè)置size
        preBufferSize = numBytes;
        //申請size大小的內(nèi)存給preBuffer
        preBuffer = malloc(preBufferSize);
        
        //為同一個(gè)值
        readPointer = preBuffer;
        writePointer = preBuffer;
    }
    return self;
}

包括一個(gè)初始化方法猎拨,去初始化preBufferSize大小的一塊內(nèi)存空間。然后3個(gè)指針都指向這個(gè)空間屠阻。

- (void)dealloc
{
    if (preBuffer)
        free(preBuffer);
}

銷毀的方法:釋放preBuffer红省。

//確認(rèn)讀的大小
- (void)ensureCapacityForWrite:(size_t)numBytes
{
    //拿到當(dāng)前可用的空間大小
    size_t availableSpace = [self availableSpace];
    
    //如果申請的大小大于可用的大小
    if (numBytes > availableSpace)
    {
        //需要多出來的大小
        size_t additionalBytes = numBytes - availableSpace;
        //新的總大小
        size_t newPreBufferSize = preBufferSize + additionalBytes;
        //重新去分配preBuffer
        uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
        
        //讀的指針偏移量(已讀大小)
        size_t readPointerOffset = readPointer - preBuffer;
        //寫的指針偏移量(已寫大泄酢)
        size_t writePointerOffset = writePointer - preBuffer;
        //提前的Buffer重新復(fù)制
        preBuffer = newPreBuffer;
        //大小重新賦值
        preBufferSize = newPreBufferSize;
        
        //讀寫指針重新賦值 + 上偏移量
        readPointer = preBuffer + readPointerOffset;
        writePointer = preBuffer + writePointerOffset;
    }
}

確保prebuffer可用空間的方法:這個(gè)方法會(huì)重新分配preBuffer吧恃,直到可用大小等于傳遞進(jìn)來的numBytes,已用大小不會(huì)變麻诀。

//仍然可讀的數(shù)據(jù)痕寓,過程是先寫后讀,只有寫的大于讀的蝇闭,才能讓你繼續(xù)去讀呻率,不然沒數(shù)據(jù)可讀了
- (size_t)availableBytes
{
    return writePointer - readPointer;
}

- (uint8_t *)readBuffer
{
    return readPointer;
}

- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
{
    if (bufferPtr) *bufferPtr = readPointer;
    if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
}

//讀數(shù)據(jù)的指針
- (void)didRead:(size_t)bytesRead
{
    readPointer += bytesRead;
    //如果讀了這么多,指針和寫的指針還相同的話丁眼,說明已經(jīng)讀完筷凤,重置指針到最初的位置
    if (readPointer == writePointer)
    {
        // The prebuffer has been drained. Reset pointers.
        readPointer  = preBuffer;
        writePointer = preBuffer;
    }
}
//prebuffer的剩余空間  = preBufferSize(總大小) - (寫的頭指針 - preBuffer一開的指針,即已被寫的大忻晔亍)

- (size_t)availableSpace
{
    return preBufferSize - (writePointer - preBuffer);
}

- (uint8_t *)writeBuffer
{
    return writePointer;
}

- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
{
    if (bufferPtr) *bufferPtr = writePointer;
    if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
}

- (void)didWrite:(size_t)bytesWritten
{
    writePointer += bytesWritten;
}

- (void)reset
{
    readPointer  = preBuffer;
    writePointer = preBuffer;
}

然后就是對讀寫指針進(jìn)行處理的方法挪丢,如果讀了多少數(shù)據(jù)readPointer就后移多少,寫也是一樣卢厂。
而獲取當(dāng)前未讀數(shù)據(jù)乾蓬,則是用已寫指針-已讀指針,得到的差值慎恒,當(dāng)已讀=已寫的時(shí)候任内,說明prebuffer數(shù)據(jù)讀完,則重置讀寫指針的位置融柬,還是指向初始化位置死嗦。

講完全局緩沖區(qū)對于指針的處理,我們接著往下說
Part4.flushSSLBuffers方法:
//緩沖ssl數(shù)據(jù)
- (void)flushSSLBuffers
{
     LogTrace();
     //斷言為安全Socket
     NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
     //如果preBuffer有數(shù)據(jù)可讀,直接返回
     if ([preBuffer availableBytes] > 0)
     {
          return;
     }
     
     #if TARGET_OS_IPHONE
     //如果用的CFStream的TLS粒氧,把數(shù)據(jù)用CFStream的方式搬運(yùn)到preBuffer中
     if ([self usingCFStreamForTLS])
     {
        //如果flag為kSecureSocketHasBytesAvailable越除,而且readStream有數(shù)據(jù)可讀
          if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
          {
               LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
               
            //默認(rèn)一次讀的大小為4KB?外盯?
               CFIndex defaultBytesToRead = (1024 * 4);
               
            //用來確保有這么大的提前buffer緩沖空間
               [preBuffer ensureCapacityForWrite:defaultBytesToRead];
               //拿到寫的buffer
               uint8_t *buffer = [preBuffer writeBuffer];
               
            //從readStream中去讀摘盆, 一次就讀4KB,讀到數(shù)據(jù)后饱苟,把數(shù)據(jù)寫到writeBuffer中去   如果讀的大小小于readStream中數(shù)據(jù)流大小孩擂,則會(huì)不停的觸發(fā)callback,直到把數(shù)據(jù)讀完為止箱熬。
               CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
            //打印結(jié)果
               LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
               
            //大于0类垦,說明讀寫成功
               if (result > 0)
               {
                //把寫的buffer頭指針,移動(dòng)result個(gè)偏移量
                    [preBuffer didWrite:result];
               }
               
            //把kSecureSocketHasBytesAvailable 仍然可讀的標(biāo)記移除
               flags &= ~kSecureSocketHasBytesAvailable;
          }
          
          return;
     }
     
     #endif
     
    //不用CFStream的處理方法
    
    //先設(shè)置一個(gè)預(yù)估可用的大小
     __block NSUInteger estimatedBytesAvailable = 0;
     //更新預(yù)估可用的Block
     dispatch_block_t updateEstimatedBytesAvailable = ^{
          
        //預(yù)估大小 = 未讀的大小 + SSL的可讀大小
          estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
          

          size_t sslInternalBufSize = 0;
        //獲取到ssl上下文的大小坦弟,從sslContext中
          SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
          //再加上下文的大小
          estimatedBytesAvailable += sslInternalBufSize;
     };
     
    //調(diào)用這個(gè)Block
     updateEstimatedBytesAvailable();
     
    //如果大于0护锤,說明有數(shù)據(jù)可讀
     if (estimatedBytesAvailable > 0)
     {
        
          LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
          
        //標(biāo)志,循環(huán)是否結(jié)束,SSL的方式是會(huì)阻塞的酿傍,直到讀的數(shù)據(jù)有estimatedBytesAvailable大小為止烙懦,或者出錯(cuò)
          BOOL done = NO;
          do
          {
               LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
               
               // Make sure there's enough room in the prebuffer
               //確保有足夠的空間給prebuffer
               [preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
               
               // Read data into prebuffer
               //拿到寫的buffer
               uint8_t *buffer = [preBuffer writeBuffer];
               size_t bytesRead = 0;
               //用SSLRead函數(shù)去讀,讀到后赤炒,把數(shù)據(jù)寫到buffer中氯析,estimatedBytesAvailable為需要讀的大小,bytesRead這一次實(shí)際讀到字節(jié)大小莺褒,為sslContext上下文
               OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
               LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
               
            //把寫指針后移bytesRead大小
               if (bytesRead > 0)
               {
                    [preBuffer didWrite:bytesRead];
               }
               
               LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
               
            //如果讀數(shù)據(jù)出現(xiàn)錯(cuò)誤
               if (result != noErr)
               {
                    done = YES;
               }
               else
               {
                //在更新一下可讀的數(shù)據(jù)大小
                    updateEstimatedBytesAvailable();
               }
               
          }
        //只有done為NO,而且 estimatedBytesAvailable大于0才繼續(xù)循環(huán)
        while (!done && estimatedBytesAvailable > 0);
     }
}

這個(gè)方法有點(diǎn)略長掩缓,包含了兩種SSL的數(shù)據(jù)處理:

  1. CFStream類型:我們會(huì)調(diào)用下面這個(gè)函數(shù)去從stream并且讀取數(shù)據(jù)并解密:
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);

數(shù)據(jù)被讀取到后,直接轉(zhuǎn)移到了prebuffer中遵岩,并且調(diào)用:

[preBuffer didWrite:result];

讓寫指針后移讀取到的數(shù)據(jù)大小你辣。
這里有兩個(gè)關(guān)于CFReadStreamRead方法巡通,需要注意的問題:
1)就是我們調(diào)用它去讀取4KB數(shù)據(jù),并不僅僅是只讀這么多舍哄,而是因?yàn)檫@個(gè)方法是會(huì)遞歸調(diào)用的宴凉,它每次只讀4KB,直到把stream中的數(shù)據(jù)讀完表悬。
2)我們之前設(shè)置的CFStream函數(shù)的回調(diào)弥锄,在數(shù)據(jù)來了之后只會(huì)被觸發(fā)一次,以后數(shù)據(jù)再來都不會(huì)觸發(fā)蟆沫。直到我們調(diào)用這個(gè)方法籽暇,把stream中的數(shù)據(jù)讀完,下次再來數(shù)據(jù)才會(huì)觸發(fā)函數(shù)回調(diào)饭庞。這也是我們在使用CFStream的時(shí)候戒悠,不需要擔(dān)心像source那樣,有數(shù)據(jù)會(huì)不斷的被觸發(fā)回調(diào)舟山,而需要掛起像source那樣掛起stream(實(shí)際也沒有這樣的方法)救崔。

  1. SSL安全通道類型:這里我們主要是循環(huán)去調(diào)用下面這個(gè)函數(shù)去讀取數(shù)據(jù):
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);

其他的基本和CFStream一致

這里需要注意的是SSLRead這個(gè)方法,并不是直接從我們的socket中獲取到的數(shù)據(jù)捏顺,而是從我們一開始綁定的SSL回調(diào)函數(shù)中,得到數(shù)據(jù)纬黎。而回調(diào)函數(shù)本身幅骄,也需要調(diào)用read函數(shù)從socket中獲取到加密的數(shù)據(jù)。然后再經(jīng)由SSLRead這個(gè)方法本今,數(shù)據(jù)被解密拆座,并且傳遞給buffer

至于SSLRead綁定的回調(diào)函數(shù)冠息,是怎么處理數(shù)據(jù)讀取的挪凑,因?yàn)樗幚頂?shù)據(jù)的流程,和我們doReadData后續(xù)數(shù)據(jù)讀取處理基本相似逛艰,所以現(xiàn)在暫時(shí)不提躏碳。

我們繞了一圈,講完了這個(gè)包為空或者當(dāng)前暫停狀態(tài)下的前置處理散怖,總結(jié)一下:
  1. 就是如果是SSL類型的數(shù)據(jù)菇绵,那么先解密了,緩沖到prebuffer中去镇眷。
  2. 判斷當(dāng)前socket可讀數(shù)據(jù)大于0咬最,非CFStreamSSL類型,則掛起source欠动,防止反復(fù)觸發(fā)永乌。
Part5.接著我們開始doReadData正常數(shù)據(jù)處理流程:

首先它大的方向,依然是分為3種類型的數(shù)據(jù)處理:
1.SSL安全通道; 2.CFStream類型SSL翅雏; 3.普通數(shù)據(jù)傳輸圈驼。
因?yàn)檫@3種類型的代碼,重復(fù)部分較大枚荣,處理流程基本類似碗脊,只不過調(diào)用讀取方法所有區(qū)別:

//1.
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
//2.
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
//3.
ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);

SSLRead回調(diào)函數(shù)內(nèi)部,也調(diào)用了第3種read讀取橄妆,這個(gè)我們后面會(huì)說衙伶。
現(xiàn)在這里我們將跳過前兩種(方法部分調(diào)用可以見上面的flushSSLBuffers方法),只講第3種普通數(shù)據(jù)的讀取操作害碾,而SSL的讀取操作矢劲,基本一致。

先來看看當(dāng)前數(shù)據(jù)包任務(wù)是否完成慌随,是如何定義的:

由于框架提供的對外read接口:

- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;

將數(shù)據(jù)讀取是否完成的操作芬沉,大致分為這3個(gè)類型:
1.全讀;2讀取一定的長度阁猜;3讀取到某個(gè)標(biāo)記符為止丸逸。

當(dāng)且僅當(dāng)上面3種類型對應(yīng)的操作完成,才視作當(dāng)前包任務(wù)完成剃袍,才會(huì)回調(diào)我們在類中聲明的讀取消息的代理:

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

否則就等待著黄刚,直到當(dāng)前數(shù)據(jù)包任務(wù)完成。

然后我們讀取數(shù)據(jù)的流程大致如下:

先從prebuffer中去讀取民效,如果讀完了憔维,當(dāng)前數(shù)據(jù)包任務(wù)仍未完成,那么再從socket中去讀取畏邢。
而判斷包是否讀完业扒,都是用我們上面的3種類型,來對應(yīng)處理的舒萎。

講了半天理論程储,想必大家看的有點(diǎn)不耐煩了,接下來看看代碼實(shí)際是如何處理的吧:

step1:從prebuffer中讀取數(shù)據(jù):
//先從提前緩沖區(qū)去讀臂寝,如果緩沖區(qū)可讀大小大于0
if ([preBuffer availableBytes] > 0)
{
     // There are 3 types of read packets:
     // 
     // 1) Read all available data.
     // 2) Read a specific length of data.
     // 3) Read up to a particular terminator.
     //3種類型的讀法虱肄,1、全讀交煞、2咏窿、讀取特定長度、3素征、讀取到一個(gè)明確的界限
   
     NSUInteger bytesToCopy;
     
   //如果當(dāng)前讀的數(shù)據(jù)界限不為空
     if (currentRead->term != nil)
     {
          // Read type #3 - read up to a terminator
          //直接讀到界限
          bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
     }
     else
     {
          // Read type #1 or #2
          //讀取數(shù)據(jù)集嵌,讀到指定長度或者數(shù)據(jù)包的長度為止
          bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
     }
     
     // Make sure we have enough room in the buffer for our read.
     //從上兩步拿到我們需要讀的長度萝挤,去看看有沒有空間去存儲(chǔ)
     [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
     
     // Copy bytes from prebuffer into packet buffer

   //拿到我們需要追加數(shù)據(jù)的指針位置
#pragma mark - 不明白
   //當(dāng)前讀的數(shù)據(jù) + 開始偏移 + 已經(jīng)讀完的?根欧?
     uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
                                                                       currentRead->bytesDone;
     //從prebuffer處復(fù)制過來數(shù)據(jù)怜珍,bytesToCopy長度
     memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
     
     // Remove the copied bytes from the preBuffer
   //從preBuffer移除掉已經(jīng)復(fù)制的數(shù)據(jù)
     [preBuffer didRead:bytesToCopy];
     
   
     LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
     
     // Update totals
     
   //已讀的數(shù)據(jù)加上
     currentRead->bytesDone += bytesToCopy;
   //當(dāng)前已讀的數(shù)據(jù)加上
     totalBytesReadForCurrentRead += bytesToCopy;
     
     // Check to see if the read operation is done
     //判斷是不是讀完了
     if (currentRead->readLength > 0)
     {
          // Read type #2 - read a specific length of data
          //如果已讀 == 需要讀的長度,說明已經(jīng)讀完
          done = (currentRead->bytesDone == currentRead->readLength);
     }
   //判斷界限標(biāo)記
     else if (currentRead->term != nil)
     {
          // Read type #3 - read up to a terminator
          
          // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
          //如果沒做完凤粗,且讀的最大長度大于0酥泛,去判斷是否溢出
          if (!done && currentRead->maxLength > 0)
          {
               // We're not done and there's a set maxLength.
               // Have we reached that maxLength yet?
               
           //如果已讀的大小大于最大的大小,則報(bào)溢出錯(cuò)誤
               if (currentRead->bytesDone >= currentRead->maxLength)
               {
                    error = [self readMaxedOutError];
               }
          }
     }
     else
     {
          // Read type #1 - read all available data
          // 
          // We're done as soon as
          // - we've read all available data (in prebuffer and socket)
          // - we've read the maxLength of read packet.
          //判斷已讀大小和最大大小是否相同嫌拣,相同則讀完
          done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
     }
     
}

這個(gè)方法就是利用我們之前提到的3種類型柔袁,來判斷數(shù)據(jù)包需要讀取的長度,然后調(diào)用:

memcpy(buffer, [preBuffer readBuffer], bytesToCopy);

把數(shù)據(jù)從preBuffer中异逐,移到了currentRead數(shù)據(jù)包中捶索。

step2:從socket中讀取數(shù)據(jù):
// 從socket中去讀取

//是否讀到EOFException ,這個(gè)錯(cuò)誤指的是文件結(jié)尾了還在繼續(xù)讀灰瞻,就會(huì)導(dǎo)致這個(gè)錯(cuò)誤被拋出
BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO;  // Nothing more to read via socket (end of file)

//如果沒完成腥例,且沒錯(cuò),沒讀到結(jié)尾酝润,且沒有可讀數(shù)據(jù)了
BOOL waiting   = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more

//如果沒完成燎竖,且沒錯(cuò),沒讀到結(jié)尾要销,有可讀數(shù)據(jù)
if (!done && !error && !socketEOF && hasBytesAvailable)
{
   //斷言底瓣,有可讀數(shù)據(jù)
     NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
   //是否讀到preBuffer中去
   BOOL readIntoPreBuffer = NO;
     uint8_t *buffer = NULL;
     size_t bytesRead = 0;
     
   //如果flag標(biāo)記為安全socket
     if (flags & kSocketSecure)
     {
       //...類似flushSSLBuffer的一系列操作
     }
     else
     {
          // Normal socket operation
          //普通的socket 操作
       
          NSUInteger bytesToRead;
          
          // There are 3 types of read packets:
          //
          // 1) Read all available data.
          // 2) Read a specific length of data.
          // 3) Read up to a particular terminator.
          
       //和上面類似,讀取到邊界標(biāo)記蕉陋??不是吧
          if (currentRead->term != nil)
          {
               // Read type #3 - read up to a terminator
               
           //讀這個(gè)長度拨扶,如果到maxlength凳鬓,就用maxlength』济瘢看如果可用空間大于需要讀的空間缩举,則不用prebuffer
               bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
                                                    shouldPreBuffer:&readIntoPreBuffer];
          }
       
          else
          {
               // Read type #1 or #2
               //直接讀這個(gè)長度,如果到maxlength匹颤,就用maxlength
               bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
          }
          
       //大于最大值仅孩,則先讀最大值
          if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
               bytesToRead = SIZE_MAX;
          }
          
          // Make sure we have enough room in the buffer for our read.
          //
          // We are either reading directly into the currentRead->buffer,
          // or we're reading into the temporary preBuffer.
          
          if (readIntoPreBuffer)
          {
               [preBuffer ensureCapacityForWrite:bytesToRead];
               
               buffer = [preBuffer writeBuffer];
          }
          else
          {
               [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
               
               buffer = (uint8_t *)[currentRead->buffer mutableBytes]
                      + currentRead->startOffset
                      + currentRead->bytesDone;
          }
          
          // Read data into buffer
          
          int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
#pragma mark - 開始讀取數(shù)據(jù),最普通的形式 read
       
       //讀數(shù)據(jù)
          ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
          LogVerbose(@"read from socket = %i", (int)result);
       //讀取錯(cuò)誤
          if (result < 0)
          {
           //EWOULDBLOCK IO阻塞
               if (errno == EWOULDBLOCK)
               //先等待
                    waiting = YES;
               else
               //得到錯(cuò)誤
                    error = [self errnoErrorWithReason:@"Error in read() function"];
               //把可讀取的長度設(shè)置為0
               socketFDBytesAvailable = 0;
          }
       //讀到邊界了
          else if (result == 0)
          {
               socketEOF = YES;
               socketFDBytesAvailable = 0;
          }
       //正常
          else
          {
           //設(shè)置讀到的數(shù)據(jù)長度
               bytesRead = result;
               
           //如果讀到的數(shù)據(jù)小于應(yīng)該讀的長度印蓖,說明這個(gè)包沒讀完
               if (bytesRead < bytesToRead)
               {
                    // The read returned less data than requested.
                    // This means socketFDBytesAvailable was a bit off due to timing,
                    // because we read from the socket right when the readSource event was firing.
                    socketFDBytesAvailable = 0;
               }
           //正常
               else
               {
               //如果 socketFDBytesAvailable比讀了的數(shù)據(jù)小的話辽慕,直接置為0
                    if (socketFDBytesAvailable <= bytesRead)
                         socketFDBytesAvailable = 0;
               //減去已讀大小
                    else
                         socketFDBytesAvailable -= bytesRead;
               }
               //如果 socketFDBytesAvailable 可讀數(shù)量為0,把讀的狀態(tài)切換為等待
               if (socketFDBytesAvailable == 0)
               {
                    waiting = YES;
               }
          }
     }

本來想講點(diǎn)什么赦肃。溅蛉。發(fā)現(xiàn)確實(shí)沒什么好講的公浪,無非就是判斷應(yīng)該讀取的長度,然后調(diào)用:

ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);

socket中得到讀取的實(shí)際長度船侧。

唯一需要講一下的可能是數(shù)據(jù)流向的問題欠气,這里調(diào)用:`

bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable shouldPreBuffer:&readIntoPreBuffer];

來判斷數(shù)據(jù)是否先流向prebuffer,還是直接流向currentRead镜撩,而SSL的讀取中也有類似方法:

- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr

這個(gè)方法核心的思路就是预柒,如果當(dāng)前讀取包,長度給明了袁梗,則直接流向currentRead宜鸯,如果數(shù)據(jù)長度不清楚,那么則去判斷這一次讀取的長度围段,和currentRead可用空間長度去對比顾翼,如果長度比currentRead可用空間小,則流向currentRead奈泪,否則先用prebuffer來緩沖适贸。

至于細(xì)節(jié)方面,大家對著github中的源碼注釋看看吧涝桅,這么大篇幅的業(yè)務(wù)代碼拜姿,一行行講確實(shí)沒什么意義。

走完這兩步讀取冯遂,接著就是第三步:

step3:判斷數(shù)據(jù)包完成程度:

這里有3種情況:
1.數(shù)據(jù)包剛好讀完蕊肥;2.數(shù)據(jù)粘包;3.數(shù)據(jù)斷包蛤肌;
注:這里判斷粘包斷包的長度壁却,都是我們一開始調(diào)用read方法給的長度或者分界符得出的。

很顯然裸准,第一種就什么都不用處理展东,完美匹配。
第二種情況炒俱,我們把需要的長度放到currentRead盐肃,多余的長度放到prebuffer中去。
第三種情況权悟,數(shù)據(jù)還沒讀完砸王,我們暫時(shí)為未讀完。

這里就不貼代碼了峦阁。

就這樣普通讀取數(shù)據(jù)的整個(gè)流程就走完了谦铃,而SSL的兩種模式,和上述基本一致榔昔。

我們接著根據(jù)之前讀取的結(jié)果荷辕,來判斷數(shù)據(jù)是否讀完:

//檢查是否讀完
if (done)
{
    //完成這次數(shù)據(jù)的讀取
    [self completeCurrentRead];
    //如果沒出錯(cuò)凿跳,沒有到邊界,prebuffer中還有可讀數(shù)據(jù)
    if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
    {
        //讓讀操作離隊(duì),繼續(xù)進(jìn)行下一次讀取
        [self maybeDequeueRead];
    }
}

如果讀完疮方,則去做讀完的操作控嗜,并且進(jìn)行下一次讀取。

我們來看看讀完的操作:
//完成了這次的讀數(shù)據(jù)
- (void)completeCurrentRead
{
    LogTrace();
    //斷言currentRead
    NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
    
    //結(jié)果數(shù)據(jù)
    NSData *result = nil;
    
    //如果是我們自己創(chuàng)建的Buffer
    if (currentRead->bufferOwner)
    {
        // We created the buffer on behalf of the user.
        // Trim our buffer to be the proper size.
        //修剪buffer到合適的大小
        //把大小設(shè)置到我們讀取到的大小
        [currentRead->buffer setLength:currentRead->bytesDone];
        //賦值給result
        result = currentRead->buffer;
    }
    else
    {
        // We did NOT create the buffer.
        // The buffer is owned by the caller.
        // Only trim the buffer if we had to increase its size.
        //這是調(diào)用者的data骡显,我們只會(huì)去加大尺寸
        if ([currentRead->buffer length] > currentRead->originalBufferLength)
        {
            //拿到的讀的size
            NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
            //拿到原始尺寸
            NSUInteger origSize = currentRead->originalBufferLength;
            
            //取得最大的
            NSUInteger buffSize = MAX(readSize, origSize);
            //把buffer設(shè)置為較大的尺寸
            [currentRead->buffer setLength:buffSize];
        }
        //拿到數(shù)據(jù)的頭指針
        uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
        
        //reslut為疆栏,從頭指針開始到長度為寫的長度 freeWhenDone為YES,創(chuàng)建完就釋放buffer
        result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
    }
    
    __strong id theDelegate = delegate;

#pragma mark -總算到調(diào)用代理方法惫谤,接受到數(shù)據(jù)了
    if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
    {
        //拿到當(dāng)前的數(shù)據(jù)包
        GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
        
        dispatch_async(delegateQueue, ^{ @autoreleasepool {
            //把result在代理queue中回調(diào)出去壁顶。
            [theDelegate socket:self didReadData:result withTag:theRead->tag];
        }});
    }
    //取消掉讀取超時(shí)
    [self endCurrentRead];
}

這里對currentReaddata做了個(gè)長度的設(shè)置碌尔。然后調(diào)用代理把最終包給回調(diào)出去修档。最后關(guān)掉我們之前提到的讀取超時(shí)。

還是回到doReadData窖杀,就剩下最后一點(diǎn)處理了:

//如果這次讀的數(shù)量大于0
else if (totalBytesReadForCurrentRead > 0)
{
    // We're not done read type #2 or #3 yet, but we have read in some bytes

    __strong id theDelegate = delegate;
    
    //如果響應(yīng)讀數(shù)據(jù)進(jìn)度的代理
    if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
    {
        long theReadTag = currentRead->tag;
        
        //代理queue中回調(diào)出去
        dispatch_async(delegateQueue, ^{ @autoreleasepool {
            
            [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
        }});
    }
}

這里未完成蝴猪,如果這次讀取大于0调衰,如果響應(yīng)讀取進(jìn)度的代理,則把當(dāng)前進(jìn)度回調(diào)出去自阱。

最后檢查錯(cuò)誤:
//檢查錯(cuò)誤
if (error)
{
    //如果有錯(cuò)直接報(bào)錯(cuò)斷開連接
    [self closeWithError:error];
}
//如果是讀到邊界錯(cuò)誤
else if (socketEOF)
{
    [self doReadEOF];
}

//如果是等待
else if (waiting)
{
    //如果用的是CFStream,則讀取數(shù)據(jù)和source無關(guān)
    //非CFStream形式
    if (![self usingCFStreamForTLS])
    {
        // Monitor the socket for readability (if we're not already doing so)
        //重新恢復(fù)source
        [self resumeReadSource];
    }
}

如果有錯(cuò)嚎莉,直接斷開socket,如果是邊界錯(cuò)誤沛豌,調(diào)用邊界錯(cuò)誤處理趋箩,如果是等待,說明當(dāng)前包還沒讀完加派,如果非CFStreamTLS叫确,則恢復(fù)source,等待下一次數(shù)據(jù)到達(dá)的觸發(fā)芍锦。

關(guān)于這個(gè)讀取邊界錯(cuò)誤EOF,這里我簡單的提下竹勉,其實(shí)它就是服務(wù)端發(fā)出一個(gè)邊界錯(cuò)誤,說明不會(huì)再有數(shù)據(jù)發(fā)送給我們了醉旦。我們講無法再接收到數(shù)據(jù),但是我們其實(shí)還是可以寫數(shù)據(jù)桨啃,發(fā)送給服務(wù)端的车胡。

doReadEOF這個(gè)方法的處理,就是做了這么一件事照瘾。判斷我們是否需要這種不可讀匈棘,只能寫的連接。

我們來簡單看看這個(gè)方法:
Part6.讀取邊界錯(cuò)誤處理:
//讀到EOFException析命,邊界錯(cuò)誤
- (void)doReadEOF
{
    LogTrace();
   //這個(gè)方法可能被調(diào)用很多次主卫,如果讀到EOF的時(shí)候逃默,還有數(shù)據(jù)在prebuffer中,在調(diào)用doReadData之后簇搅?完域? 這個(gè)方法可能被持續(xù)的調(diào)用
    
    //標(biāo)記為讀EOF
    flags |= kSocketHasReadEOF;
    
    //如果是安全socket
    if (flags & kSocketSecure)
    {
        //去刷新sslbuffer中的數(shù)據(jù)
        [self flushSSLBuffers];
    }
    
    //標(biāo)記是否應(yīng)該斷開連接
    BOOL shouldDisconnect = NO;
    NSError *error = nil;
    
    //如果狀態(tài)為開始讀寫TLS
    if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
    {
        //我們得到EOF在開啟TLS之前,這個(gè)TLS握手是不可能的瘩将,因此這是不可恢復(fù)的錯(cuò)誤
        
        //標(biāo)記斷開連接
        shouldDisconnect = YES;
        //如果是安全的TLS吟税,賦值錯(cuò)誤
        if ([self usingSecureTransportForTLS])
        {
            error = [self sslError:errSSLClosedAbort];
        }
    }
    //如果是讀流關(guān)閉狀態(tài)
    else if (flags & kReadStreamClosed)
    {
        
        //不應(yīng)該被關(guān)閉
        shouldDisconnect = NO;
    }
    else if ([preBuffer availableBytes] > 0)
    {
        //仍然有數(shù)據(jù)可讀的時(shí)候不關(guān)閉
        shouldDisconnect = NO;
    }
    else if (config & kAllowHalfDuplexConnection)
    {
        
        //拿到socket
        int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
        
        //輪詢用的結(jié)構(gòu)體
        
        /*
         struct pollfd {
         int fd;        //文件描述符
         short events;  //要求查詢的事件掩碼  監(jiān)聽的
         short revents; //返回的事件掩碼   實(shí)際發(fā)生的
         };
         */
        
        struct pollfd pfd[1];
        pfd[0].fd = socketFD;
        //寫數(shù)據(jù)不會(huì)導(dǎo)致阻塞。
        pfd[0].events = POLLOUT;
        //這個(gè)為當(dāng)前實(shí)際發(fā)生的事情
        pfd[0].revents = 0;
        
        /*
         poll函數(shù)使用pollfd類型的結(jié)構(gòu)來監(jiān)控一組文件句柄姿现,ufds是要監(jiān)控的文件句柄集合肠仪,nfds是監(jiān)控的文件句柄數(shù)量,timeout是等待的毫秒數(shù)备典,這段時(shí)間內(nèi)無論I/O是否準(zhǔn)備好异旧,poll都會(huì)返回。timeout為負(fù)數(shù)表示無線等待提佣,timeout為0表示調(diào)用后立即返回吮蛹。執(zhí)行結(jié)果:為0表示超時(shí)前沒有任何事件發(fā)生;-1表示失敻湟馈匹涮;成功則返回結(jié)構(gòu)體中revents不為0的文件描述符個(gè)數(shù)。pollfd結(jié)構(gòu)監(jiān)控的事件類型如下:
         int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
         */
        //阻塞的槐壳,但是timeout為0然低,則不阻塞,直接返回
        poll(pfd, 1, 0);
        
        //如果被觸發(fā)的事件是寫數(shù)據(jù)
        if (pfd[0].revents & POLLOUT)
        {
            // Socket appears to still be writeable
            
            //則標(biāo)記為不關(guān)閉
            shouldDisconnect = NO;
            //標(biāo)記為讀流關(guān)閉
            flags |= kReadStreamClosed;
            
            // Notify the delegate that we're going half-duplex
            //通知代理务唐,我們開始半雙工
            __strong id theDelegate = delegate;

            //調(diào)用已經(jīng)關(guān)閉讀流的代理方法
            if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
            {
                dispatch_async(delegateQueue, ^{ @autoreleasepool {
                    
                    [theDelegate socketDidCloseReadStream:self];
                }});
            }
        }
        else
        {
            //標(biāo)記為斷開
            shouldDisconnect = YES;
        }
    }
    else
    {
        shouldDisconnect = YES;
    }
    
    //如果應(yīng)該斷開
    if (shouldDisconnect)
    {
        if (error == nil)
        {
            //判斷是否是安全TLS傳輸
            if ([self usingSecureTransportForTLS])
            {
                ///標(biāo)記錯(cuò)誤信息
                if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
                {
                    error = [self sslError:sslErrCode];
                }
                else
                {
                    error = [self connectionClosedError];
                }
            }
            else
            {
                error = [self connectionClosedError];
            }
        }
        //關(guān)閉socket
        [self closeWithError:error];
    }
    //不斷開
    else
    {
        //如果不是用CFStream流
        if (![self usingCFStreamForTLS])
        {
            // Suspend the read source (if needed)
            //掛起讀source
            [self suspendReadSource];
        }
    }
}

簡單說一下雳攘,這個(gè)方法主要是對socket是否需要主動(dòng)關(guān)閉進(jìn)行了判斷:這里僅僅以下3種情況,不會(huì)關(guān)閉socket

  1. 讀流已經(jīng)是關(guān)閉狀態(tài)(如果加了這個(gè)標(biāo)記枫笛,說明為半雙工連接狀態(tài))吨灭。
  • preBuffer中還有可讀數(shù)據(jù),我們需要等數(shù)據(jù)讀完才能關(guān)閉連接刑巧。
  • 配置標(biāo)記為kAllowHalfDuplexConnection喧兄,我們則要開始半雙工處理。我們調(diào)用了:
poll(pfd, 1, 0);

函數(shù)啊楚,如果觸發(fā)了寫事件POLLOUT吠冤,說明我們半雙工連接成功,則我們可以在讀流關(guān)閉的狀態(tài)下恭理,仍然可以向服務(wù)器寫數(shù)據(jù)拯辙。

其他情況下,一律直接關(guān)閉socket颜价。
而不關(guān)閉的情況下涯保,我們會(huì)掛起source诉濒。這樣我們就只能可寫不可讀了。

最后還是提下SSL的回調(diào)方法夕春,數(shù)據(jù)解密的地方未荒。兩種模式的回調(diào);

Part7.兩種SSL數(shù)據(jù)解密位置:

1.CFStream:當(dāng)我們調(diào)用:

CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);

數(shù)據(jù)就會(huì)被解密撇他。
2.SSL安全通道:當(dāng)我們調(diào)用:

OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);

會(huì)觸發(fā)SSL綁定的函數(shù)回調(diào):

//讀函數(shù)
static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
{
    //拿到socket
    GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
    
    //斷言當(dāng)前為socketQueue
    NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
    
    //讀取數(shù)據(jù)茄猫,并且返回狀態(tài)碼
    return [asyncSocket sslReadWithBuffer:data length:dataLength];
}

接著我們在下面的方法進(jìn)行了數(shù)據(jù)讀取:

//SSL讀取數(shù)據(jù)最終方法
- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
{
    //...
    ssize_t result = read(socketFD, buf, bytesToRead);
    //....
}

其實(shí)read這一步困肩,數(shù)據(jù)是沒有被解密的划纽,然后傳遞回SSLReadFunction,在傳遞到SSLRead內(nèi)部锌畸,數(shù)據(jù)被解密勇劣。

尾聲:

這個(gè)系列就剩下最后一篇Write了。由于內(nèi)容相對比較簡單潭枣,預(yù)計(jì)就一篇寫完了比默。
如果一直看到這里的朋友,會(huì)發(fā)現(xiàn)盆犁,相對之前有些內(nèi)容命咐,講解沒那么詳細(xì)了。其實(shí)原因主要有兩點(diǎn)谐岁,一是代碼數(shù)量龐大醋奠,確實(shí)無法詳細(xì)。二是樓主對這個(gè)系列寫的有點(diǎn)不耐煩伊佃,想要盡快結(jié)束了..
不過至少整篇的源碼注釋在github上是有的窜司,我覺得大家自己去對著源碼去閱讀理解同樣重要,如果一直逐字逐行的去講航揉,那就真的沒什么意義了塞祈。

畢竟授之以魚,不如授之以漁帅涂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末议薪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子媳友,更是在濱河造成了極大的恐慌斯议,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆锦,死亡現(xiàn)場離奇詭異捅位,居然都是意外死亡轧葛,警方通過查閱死者的電腦和手機(jī)搂抒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門艇搀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人求晶,你說我怎么就攤上這事焰雕。” “怎么了芳杏?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵矩屁,是天一觀的道長。 經(jīng)常有香客問我爵赵,道長吝秕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任空幻,我火速辦了婚禮烁峭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秕铛。我一直安慰自己约郁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布但两。 她就那樣靜靜地躺著鬓梅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谨湘。 梳的紋絲不亂的頭發(fā)上绽快,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音悲关,去河邊找鬼谎僻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寓辱,可吹牛的內(nèi)容都是我干的艘绍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秫筏,長吁一口氣:“原來是場噩夢啊……” “哼诱鞠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起这敬,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤航夺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后崔涂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阳掐,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缭保。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汛闸。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖艺骂,靈堂內(nèi)的尸體忽然破棺而出诸老,到底是詐尸還是另有隱情,我是刑警寧澤钳恕,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布别伏,位于F島的核電站,受9級特大地震影響忧额,放射性物質(zhì)發(fā)生泄漏厘肮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一睦番、第九天 我趴在偏房一處隱蔽的房頂上張望轴脐。 院中可真熱鬧,春花似錦抡砂、人聲如沸大咱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碴巾。三九已至,卻和暖如春丑搔,著一層夾襖步出監(jiān)牢的瞬間厦瓢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工啤月, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮仇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓谎仲,卻偏偏與公主長得像浙垫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子郑诺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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