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

接上篇:iOS即時通信之CocoaAsyncSocket源碼解析四         原文html

前言:

本文爲CocoaAsyncSocket Read篇終,將重點涉及該框架是如何利用緩衝區對數據進行讀取、以及各類狀況下的數據包處理,其中還包括普通的、和基於TLS的不一樣讀取操做等等。git

正文:

前文講完了兩次TLS創建鏈接的流程,接着就是本篇的重頭戲了:doReadData方法。在這裏我不許備直接把這個整個方法列出來,由於就光這一個方法,加上註釋有1200行,整個貼過來也沒法展開描述,因此在這裏我打算對它分段進行講解:github

注:如下代碼整個包括在doReadData大括號中:數組

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

Part1.沒法正常讀取數據時的前置處理:

 1 //若是當前讀取的包爲空,或者flag爲讀取中止,這兩種狀況是不能去讀取數據的
 2 if ((currentRead == nil) || (flags & kReadsPaused))
 3 {
 4      LogVerbose(@"No currentRead or kReadsPaused");
 5 
 6      // Unable to read at this time
 7      //若是是安全的通訊,經過TLS/SSL
 8      if (flags & kSocketSecure)
 9      {
10        //刷新SSLBuffer,把數據從鏈路上移到prebuffer中 (當前不讀取數據的時候作)
11           [self flushSSLBuffers];
12      }
13 
14    //判斷是否用的是 CFStream的TLS
15      if ([self usingCFStreamForTLS])
16      {
17 
18      }
19      else
20      {
21        //掛起source
22           if (socketFDBytesAvailable > 0)
23           {
24                [self suspendReadSource];
25           }
26      }
27      return;
28 }

當咱們當前讀取的包是空或者標記爲讀中止狀態的時候,則不會去讀取數據。
前者不難理解,由於咱們要讀取的數據最終是要傳給currentRead中去的,因此若是currentRead爲空,咱們去讀數據也沒有意義。
後者kReadsPaused標記是從哪裏加上的呢?咱們全局搜索一下,發現它才read超時的時候被添加。
講到這咱們順便來看這個讀取超時的一個邏輯,咱們每次作讀取任務傳進來的超時,都會調用這麼一個方法:安全

[self setupReadTimerWithTimeout:currentRead->timeout];
 1 //初始化讀的超時
 2 - (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
 3 {
 4     if (timeout >= 0.0)
 5     {
 6         //生成一個定時器source
 7         readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
 8 
 9         __weak GCDAsyncSocket *weakSelf = self;
10 
11         //句柄
12         dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
13         #pragma clang diagnostic push
14         #pragma clang diagnostic warning "-Wimplicit-retain-self"
15 
16             __strong GCDAsyncSocket *strongSelf = weakSelf;
17             if (strongSelf == nil) return_from_block;
18 
19             //執行超時操做
20             [strongSelf doReadTimeout];
21 
22         #pragma clang diagnostic pop
23         }});
24 
25         #if !OS_OBJECT_USE_OBJC
26         dispatch_source_t theReadTimer = readTimer;
27 
28         //取消的句柄
29         dispatch_source_set_cancel_handler(readTimer, ^{
30         #pragma clang diagnostic push
31         #pragma clang diagnostic warning "-Wimplicit-retain-self"
32 
33             LogVerbose(@"dispatch_release(readTimer)");
34             dispatch_release(theReadTimer);
35 
36         #pragma clang diagnostic pop
37         });
38         #endif
39 
40         //定時器延時 timeout時間執行
41         dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
42         //間隔爲永遠,即只執行一次
43         dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
44         dispatch_resume(readTimer);
45     }
46

這個方法定義了一個GCD定時器,這個定時器只執行一次,間隔就是咱們的超時,很顯然這是一個延時執行,那小夥伴要問了,這裏爲何咱們不用NSTimer或者下面這種方式:bash

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

緣由很簡單,performSelector是基於runloop才能使用的,它本質是轉化成runloop基於非端口的源source0。很顯然咱們所在的socketQueue開闢出來的線程,並無添加一個runloop。而NSTimer也是同樣。服務器

因此這裏咱們用GCD Timer,由於它是基於XNU內核來實現的,並不須要藉助於runloopapp

這裏當超時時間間隔到達時,咱們會執行超時操做:框架

[strongSelf doReadTimeout];
 1 /執行超時操做
 2 - (void)doReadTimeout
 3 {
 4     // This is a little bit tricky.
 5     // Ideally we'd like to synchronously query the delegate about a timeout extension.
 6     // But if we do so synchronously we risk a possible deadlock.
 7     // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
 8 
 9     //由於這裏用同步容易死鎖,因此用異步從代理中回調
10 
11     //標記讀暫停
12     flags |= kReadsPaused;
13 
14     __strong id theDelegate = delegate;
15 
16     //判斷是否實現了延時  補時的代理
17     if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
18     {
19         //拿到當前讀的包
20         GCDAsyncReadPacket *theRead = currentRead;
21 
22         //代理queue中回調
23         dispatch_async(delegateQueue, ^{ @autoreleasepool {
24 
25             NSTimeInterval timeoutExtension = 0.0;
26 
27             //調用代理方法,拿到續的時長
28             timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
29                                                                          elapsed:theRead->timeout
30                                                                        bytesDone:theRead->bytesDone];
31 
32             //socketQueue中,作延時
33             dispatch_async(socketQueue, ^{ @autoreleasepool {
34 
35                 [self doReadTimeoutWithExtension:timeoutExtension];
36             }});
37         }});
38     }
39     else
40     {
41         [self doReadTimeoutWithExtension:0.0];
42     }
43 }
//作讀取數據延時
- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
    if (currentRead)
    {
        if (timeoutExtension > 0.0)
        {
            //把超時加上
            currentRead->timeout += timeoutExtension;

            // Reschedule the timer
            //從新生成時間
            dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
            //重置timer時間
            dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);

            // Unpause reads, and continue
            //在把paused標記移除
            flags &= ~kReadsPaused;
            //繼續去讀取數據
            [self doReadData];
        }
        else
        {
            //輸出讀取超時,並斷開鏈接
            LogVerbose(@"ReadTimeout");

            [self closeWithError:[self readTimeoutError]];
        }
    }
}

這裏調用了續時代理,若是咱們實現了這個代理,則能夠增長這個超時時間,而後從新生成超時定時器,移除讀取中止的標記kReadsPaused。繼續去讀取數據。
不然咱們就斷開socket
注意:這個定時器會被取消,若是當前數據包被讀取完成,這樣就不會走到定時器超時的時間,則不會斷開socket。講到這是否是你們就有印象了?這個就是以前在樓主:
iOS即時通信,從入門到「放棄」?中講過的能夠被用來作PingPong機制的原理。less

咱們接着回到doReadData中,咱們講到若是當前讀取包爲空或者狀態爲kReadsPaused,咱們就去執行一些非讀取數據的處理。
這裏咱們第一步去判斷當前鏈接是否爲kSocketSecure,也就是安全通道的TLS。若是是咱們則調用

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

按理說,咱們有當前讀取包的時候,在去從prebuffersocket中去讀取,可是這裏爲何要提早去讀呢?
咱們來看看這個框架做者的解釋:

// 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,讀取數據的時候須要解密的過程,而這個過程是費時的,咱們不必讓用戶在讀取數據的時候去等待這個解密的過程,咱們能夠提早在數據一到達,就去讀取解密。
並且這種方式,還能時刻根據TLSgoodbye包來準確的檢測到TCP斷開鏈接。

在咱們來看flushSSLBuffers方法以前,咱們先來看看這個一直提到的全局緩衝區prebuffer的定義,它其實就是下面這麼一個類的實例:

Part3.GCDAsyncSocketPreBuffer的定義

@interface GCDAsyncSocketPreBuffer : NSObject
{
    //unsigned char
    //提早的指針,指向這塊提早的緩衝區
    uint8_t *preBuffer;
    //size_t 它是一個與機器相關的unsigned類型,其大小足以保證存儲內存中對象的大小。
    //它能夠存儲在理論上是可能的任何類型的數組的最大大小
    size_t preBufferSize;
    //讀的指針
    uint8_t *readPointer;
    //寫的指針
    uint8_t *writePointer;
}

裏面存了3個指針,包括preBuffer起點指針、當前讀寫所處位置指針、以及一個preBufferSize,這個sizepreBuffer所指向的位置,在內存中分配的空間大小。

咱們來看看它的幾個方法:

//初始化
- (id)initWithCapacity:(size_t)numBytes
{
    if ((self = [super init]))
    {
        //設置size
        preBufferSize = numBytes;
        //申請size大小的內存給preBuffer
        preBuffer = malloc(preBufferSize);

        //爲同一個值
        readPointer = preBuffer;
        writePointer = preBuffer;
    }
    return self;
}

包括一個初始化方法,去初始化preBufferSize大小的一塊內存空間。而後3個指針都指向這個空間。

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

銷燬的方法:釋放preBuffer。

 1 //確認讀的大小
 2 - (void)ensureCapacityForWrite:(size_t)numBytes
 3 {
 4     //拿到當前可用的空間大小
 5     size_t availableSpace = [self availableSpace];
 6 
 7     //若是申請的大小大於可用的大小
 8     if (numBytes > availableSpace)
 9     {
10         //須要多出來的大小
11         size_t additionalBytes = numBytes - availableSpace;
12         //新的總大小
13         size_t newPreBufferSize = preBufferSize + additionalBytes;
14         //從新去分配preBuffer
15         uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
16 
17         //讀的指針偏移量(已讀大小)
18         size_t readPointerOffset = readPointer - preBuffer;
19         //寫的指針偏移量(已寫大小)
20         size_t writePointerOffset = writePointer - preBuffer;
21         //提早的Buffer從新複製
22         preBuffer = newPreBuffer;
23         //大小從新賦值
24         preBufferSize = newPreBufferSize;
25 
26         //讀寫指針從新賦值 + 上偏移量
27         readPointer = preBuffer + readPointerOffset;
28         writePointer = preBuffer + writePointerOffset;
29     }
30 }

確保prebuffer可用空間的方法:這個方法會從新分配preBuffer,直到可用大小等於傳遞進來的numBytes,已用大小不會變。

 1 //仍然可讀的數據,過程是先寫後讀,只有寫的大於讀的,才能讓你繼續去讀,否則沒數據可讀了
 2 - (size_t)availableBytes
 3 {
 4     return writePointer - readPointer;
 5 }
 6 
 7 - (uint8_t *)readBuffer
 8 {
 9     return readPointer;
10 }
11 
12 - (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
13 {
14     if (bufferPtr) *bufferPtr = readPointer;
15     if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
16 }
17 
18 //讀數據的指針
19 - (void)didRead:(size_t)bytesRead
20 {
21     readPointer += bytesRead;
22     //若是讀了這麼多,指針和寫的指針還相同的話,說明已經讀完,重置指針到最初的位置
23     if (readPointer == writePointer)
24     {
25         // The prebuffer has been drained. Reset pointers.
26         readPointer  = preBuffer;
27         writePointer = preBuffer;
28     }
29 }
30 //prebuffer的剩餘空間  = preBufferSize(總大小) - (寫的頭指針 - preBuffer一開的指針,即已被寫的大小)
31 
32 - (size_t)availableSpace
33 {
34     return preBufferSize - (writePointer - preBuffer);
35 }
36 
37 - (uint8_t *)writeBuffer
38 {
39     return writePointer;
40 }
41 
42 - (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
43 {
44     if (bufferPtr) *bufferPtr = writePointer;
45     if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
46 }
47 
48 - (void)didWrite:(size_t)bytesWritten
49 {
50     writePointer += bytesWritten;
51 }
52 
53 - (void)reset
54 {
55     readPointer  = preBuffer;
56     writePointer = preBuffer;
57 }

而後就是對讀寫指針進行處理的方法,若是讀了多少數據readPointer就後移多少,寫也是同樣。
而獲取當前未讀數據,則是用已寫指針-已讀指針,獲得的差值,當已讀=已寫的時候,說明prebuffer數據讀完,則重置讀寫指針的位置,仍是指向初始化位置。

講徹底局緩衝區對於指針的處理,咱們接着往下說

Part4.flushSSLBuffers方法:

  1 //緩衝ssl數據
  2 - (void)flushSSLBuffers
  3 {
  4      LogTrace();
  5      //斷言爲安全Socket
  6      NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
  7      //若是preBuffer有數據可讀,直接返回
  8      if ([preBuffer availableBytes] > 0)
  9      {
 10           return;
 11      }
 12 
 13      #if TARGET_OS_IPHONE
 14      //若是用的CFStream的TLS,把數據用CFStream的方式搬運到preBuffer中
 15      if ([self usingCFStreamForTLS])
 16      {
 17         //若是flag爲kSecureSocketHasBytesAvailable,並且readStream有數據可讀
 18           if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
 19           {
 20                LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
 21 
 22             //默認一次讀的大小爲4KB??
 23                CFIndex defaultBytesToRead = (1024 * 4);
 24 
 25             //用來確保有這麼大的提早buffer緩衝空間
 26                [preBuffer ensureCapacityForWrite:defaultBytesToRead];
 27                //拿到寫的buffer
 28                uint8_t *buffer = [preBuffer writeBuffer];
 29 
 30             //從readStream中去讀, 一次就讀4KB,讀到數據後,把數據寫到writeBuffer中去   若是讀的大小小於readStream中數據流大小,則會不停的觸發callback,直到把數據讀完爲止。
 31                CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
 32             //打印結果
 33                LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
 34 
 35             //大於0,說明讀寫成功
 36                if (result > 0)
 37                {
 38                 //把寫的buffer頭指針,移動result個偏移量
 39                     [preBuffer didWrite:result];
 40                }
 41 
 42             //把kSecureSocketHasBytesAvailable 仍然可讀的標記移除
 43                flags &= ~kSecureSocketHasBytesAvailable;
 44           }
 45 
 46           return;
 47      }
 48 
 49      #endif
 50 
 51     //不用CFStream的處理方法
 52 
 53     //先設置一個預估可用的大小
 54      __block NSUInteger estimatedBytesAvailable = 0;
 55      //更新預估可用的Block
 56      dispatch_block_t updateEstimatedBytesAvailable = ^{
 57 
 58         //預估大小 = 未讀的大小 + SSL的可讀大小
 59           estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
 60 
 61 
 62           size_t sslInternalBufSize = 0;
 63         //獲取到ssl上下文的大小,從sslContext中
 64           SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
 65           //再加上下文的大小
 66           estimatedBytesAvailable += sslInternalBufSize;
 67      };
 68 
 69     //調用這個Block
 70      updateEstimatedBytesAvailable();
 71 
 72     //若是大於0,說明有數據可讀
 73      if (estimatedBytesAvailable > 0)
 74      {
 75 
 76           LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
 77 
 78         //標誌,循環是否結束,SSL的方式是會阻塞的,直到讀的數據有estimatedBytesAvailable大小爲止,或者出錯
 79           BOOL done = NO;
 80           do
 81           {
 82                LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
 83 
 84                // Make sure there's enough room in the prebuffer
 85                //確保有足夠的空間給prebuffer
 86                [preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
 87 
 88                // Read data into prebuffer
 89                //拿到寫的buffer
 90                uint8_t *buffer = [preBuffer writeBuffer];
 91                size_t bytesRead = 0;
 92                //用SSLRead函數去讀,讀到後,把數據寫到buffer中,estimatedBytesAvailable爲須要讀的大小,bytesRead這一次實際讀到字節大小,爲sslContext上下文
 93                OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
 94                LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
 95 
 96             //把寫指針後移bytesRead大小
 97                if (bytesRead > 0)
 98                {
 99                     [preBuffer didWrite:bytesRead];
100                }
101 
102                LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
103 
104             //若是讀數據出現錯誤
105                if (result != noErr)
106                {
107                     done = YES;
108                }
109                else
110                {
111                 //在更新一下可讀的數據大小
112                     updateEstimatedBytesAvailable();
113                }
114 
115           }
116         //只有done爲NO,並且 estimatedBytesAvailable大於0才繼續循環
117         while (!done && estimatedBytesAvailable > 0);
118      }
119 }

這個方法有點略長,包含了兩種SSL的數據處理:

  1. CFStream類型:咱們會調用下面這個函數去從stream而且讀取數據並解密:
  2. CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);

    數據被讀取到後,直接轉移到了prebuffer中,而且調用:

  3. [preBuffer didWrite:result];

    讓寫指針後移讀取到的數據大小。
    這裏有兩個關於CFReadStreamRead方法,須要注意的問題:
    1)就是咱們調用它去讀取4KB數據,並不只僅是隻讀這麼多,而是由於這個方法是會遞歸調用的,它每次只讀4KB,直到把stream中的數據讀完。
    2)咱們以前設置的CFStream函數的回調,在數據來了以後只會被觸發一次,之後數據再來都不會觸發。直到咱們調用這個方法,把stream中的數據讀完,下次再來數據纔會觸發函數回調。這也是咱們在使用CFStream的時候,不須要擔憂像source那樣,有數據會不斷的被觸發回調,而須要掛起像source那樣掛起stream(實際也沒有這樣的方法)。

2. SSL安全通道類型:這裏咱們主要是循環去調用下面這個函數去讀取數據:

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

 

其餘的基本和CFStream一致

這裏須要注意的是SSLRead這個方法,並非直接從咱們的socket中獲取到的數據,而是從咱們一開始綁定的SSL回調函數中,獲得數據。而回調函數自己,也須要調用read函數從socket中獲取到加密的數據。而後再經由SSLRead這個方法,數據被解密,而且傳遞給buffer

至於SSLRead綁定的回調函數,是怎麼處理數據讀取的,由於它處理數據的流程,和咱們doReadData後續數據讀取處理基本類似,因此如今暫時不提。

 

咱們繞了一圈,講完了這個包爲空或者當前暫停狀態下的前置處理,總結一下:

  1. 就是若是是SSL類型的數據,那麼先解密了,緩衝到prebuffer中去。
  2. 判斷當前socket可讀數據大於0,非CFStreamSSL類型,則掛起source,防止反覆觸發。

Part5.接着咱們開始doReadData正常數據處理流程:

首先它大的方向,依然是分爲3種類型的數據處理:
1.SSL安全通道; 2.CFStream類型SSL; 3.普通數據傳輸。
由於這3種類型的代碼,重複部分較大,處理流程基本相似,只不過調用讀取方法全部區別:

//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回調函數內部,也調用了第3種read讀取,這個咱們後面會說。
如今這裏咱們將跳過前兩種(方法部分調用能夠見上面的flushSSLBuffers方法),只講第3種普通數據的讀取操做,而SSL的讀取操做,基本一致。

先來看看當前數據包任務是否完成,是如何定義的:

因爲框架提供的對外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;

將數據讀取是否完成的操做,大體分爲這3個類型:
1.全讀;2讀取必定的長度;3讀取到某個標記符爲止。 

當且僅當上面3種類型對應的操做完成,才視做當前包任務完成,纔會回調咱們在類中聲明的讀取消息的代理:

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

不然就等待着,直到當前數據包任務完成。

而後咱們讀取數據的流程大體以下:

先從prebuffer中去讀取,若是讀完了,當前數據包任務仍未完成,那麼再從socket中去讀取。
而判斷包是否讀完,都是用咱們上面的3種類型,來對應處理的。

講了半天理論,想必你們看的有點不耐煩了,接下來看看代碼實際是如何處理的吧:

step1:從prebuffer中讀取數據:

 

 

 

 1 //先從提早緩衝區去讀,若是緩衝區可讀大小大於0
 2 if ([preBuffer availableBytes] > 0)
 3 {
 4      // There are 3 types of read packets:
 5      // 
 6      // 1) Read all available data.
 7      // 2) Read a specific length of data.
 8      // 3) Read up to a particular terminator.
 9      //3種類型的讀法,一、全讀、二、讀取特定長度、三、讀取到一個明確的界限
10 
11      NSUInteger bytesToCopy;
12 
13    //若是當前讀的數據界限不爲空
14      if (currentRead->term != nil)
15      {
16           // Read type #3 - read up to a terminator
17           //直接讀到界限
18           bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
19      }
20      else
21      {
22           // Read type #1 or #2
23           //讀取數據,讀到指定長度或者數據包的長度爲止
24           bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
25      }
26 
27      // Make sure we have enough room in the buffer for our read.
28      //從上兩步拿到咱們須要讀的長度,去看看有沒有空間去存儲
29      [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
30 
31      // Copy bytes from prebuffer into packet buffer
32 
33    //拿到咱們須要追加數據的指針位置
34 #pragma mark - 不明白
35    //當前讀的數據 + 開始偏移 + 已經讀完的??
36      uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
37                                                                        currentRead->bytesDone;
38      //從prebuffer處複製過來數據,bytesToCopy長度
39      memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
40 
41      // Remove the copied bytes from the preBuffer
42    //從preBuffer移除掉已經複製的數據
43      [preBuffer didRead:bytesToCopy];
44 
45 
46      LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
47 
48      // Update totals
49 
50    //已讀的數據加上
51      currentRead->bytesDone += bytesToCopy;
52    //當前已讀的數據加上
53      totalBytesReadForCurrentRead += bytesToCopy;
54 
55      // Check to see if the read operation is done
56      //判斷是否是讀完了
57      if (currentRead->readLength > 0)
58      {
59           // Read type #2 - read a specific length of data
60           //若是已讀 == 須要讀的長度,說明已經讀完
61           done = (currentRead->bytesDone == currentRead->readLength);
62      }
63    //判斷界限標記
64      else if (currentRead->term != nil)
65      {
66           // Read type #3 - read up to a terminator
67 
68           // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
69           //若是沒作完,且讀的最大長度大於0,去判斷是否溢出
70           if (!done && currentRead->maxLength > 0)
71           {
72                // We're not done and there's a set maxLength.
73                // Have we reached that maxLength yet?
74 
75            //若是已讀的大小大於最大的大小,則報溢出錯誤
76                if (currentRead->bytesDone >= currentRead->maxLength)
77                {
78                     error = [self readMaxedOutError];
79                }
80           }
81      }
82      else
83      {
84           // Read type #1 - read all available data
85           // 
86           // We're done as soon as
87           // - we've read all available data (in prebuffer and socket)
88           // - we've read the maxLength of read packet.
89           //判斷已讀大小和最大大小是否相同,相同則讀完
90           done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
91      }
92 
93 }

這個方法就是利用咱們以前提到的3種類型,來判斷數據包須要讀取的長度,而後調用:

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

把數據從preBuffer中,移到了currentRead數據包中。

step2:從socket中讀取數據:

  1 // 從socket中去讀取
  2 
  3 //是否讀到EOFException ,這個錯誤指的是文件結尾了還在繼續讀,就會致使這個錯誤被拋出
  4 BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO;  // Nothing more to read via socket (end of file)
  5 
  6 //若是沒完成,且沒錯,沒讀到結尾,且沒有可讀數據了
  7 BOOL waiting   = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
  8 
  9 //若是沒完成,且沒錯,沒讀到結尾,有可讀數據
 10 if (!done && !error && !socketEOF && hasBytesAvailable)
 11 {
 12    //斷言,有可讀數據
 13      NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
 14    //是否讀到preBuffer中去
 15    BOOL readIntoPreBuffer = NO;
 16      uint8_t *buffer = NULL;
 17      size_t bytesRead = 0;
 18 
 19    //若是flag標記爲安全socket
 20      if (flags & kSocketSecure)
 21      {
 22        //...相似flushSSLBuffer的一系列操做
 23      }
 24      else
 25      {
 26           // Normal socket operation
 27           //普通的socket 操做
 28 
 29           NSUInteger bytesToRead;
 30 
 31           // There are 3 types of read packets:
 32           //
 33           // 1) Read all available data.
 34           // 2) Read a specific length of data.
 35           // 3) Read up to a particular terminator.
 36 
 37        //和上面相似,讀取到邊界標記??不是吧
 38           if (currentRead->term != nil)
 39           {
 40                // Read type #3 - read up to a terminator
 41 
 42            //讀這個長度,若是到maxlength,就用maxlength。看若是可用空間大於須要讀的空間,則不用prebuffer
 43                bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
 44                                                     shouldPreBuffer:&readIntoPreBuffer];
 45           }
 46 
 47           else
 48           {
 49                // Read type #1 or #2
 50                //直接讀這個長度,若是到maxlength,就用maxlength
 51                bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
 52           }
 53 
 54        //大於最大值,則先讀最大值
 55           if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
 56                bytesToRead = SIZE_MAX;
 57           }
 58 
 59           // Make sure we have enough room in the buffer for our read.
 60           //
 61           // We are either reading directly into the currentRead->buffer,
 62           // or we're reading into the temporary preBuffer.
 63 
 64           if (readIntoPreBuffer)
 65           {
 66                [preBuffer ensureCapacityForWrite:bytesToRead];
 67 
 68                buffer = [preBuffer writeBuffer];
 69           }
 70           else
 71           {
 72                [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
 73 
 74                buffer = (uint8_t *)[currentRead->buffer mutableBytes]
 75                       + currentRead->startOffset
 76                       + currentRead->bytesDone;
 77           }
 78 
 79           // Read data into buffer
 80 
 81           int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
 82 #pragma mark - 開始讀取數據,最普通的形式 read
 83 
 84        //讀數據
 85           ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
 86           LogVerbose(@"read from socket = %i", (int)result);
 87        //讀取錯誤
 88           if (result < 0)
 89           {
 90            //EWOULDBLOCK IO阻塞
 91                if (errno == EWOULDBLOCK)
 92                //先等待
 93                     waiting = YES;
 94                else
 95                //獲得錯誤
 96                     error = [self errnoErrorWithReason:@"Error in read() function"];
 97                //把可讀取的長度設置爲0
 98                socketFDBytesAvailable = 0;
 99           }
100        //讀到邊界了
101           else if (result == 0)
102           {
103                socketEOF = YES;
104                socketFDBytesAvailable = 0;
105           }
106        //正常
107           else
108           {
109            //設置讀到的數據長度
110                bytesRead = result;
111 
112            //若是讀到的數據小於應該讀的長度,說明這個包沒讀完
113                if (bytesRead < bytesToRead)
114                {
115                     // The read returned less data than requested.
116                     // This means socketFDBytesAvailable was a bit off due to timing,
117                     // because we read from the socket right when the readSource event was firing.
118                     socketFDBytesAvailable = 0;
119                }
120            //正常
121                else
122                {
123                //若是 socketFDBytesAvailable比讀了的數據小的話,直接置爲0
124                     if (socketFDBytesAvailable <= bytesRead)
125                          socketFDBytesAvailable = 0;
126                //減去已讀大小
127                     else
128                          socketFDBytesAvailable -= bytesRead;
129                }
130                //若是 socketFDBytesAvailable 可讀數量爲0,把讀的狀態切換爲等待
131                if (socketFDBytesAvailable == 0)
132                {
133                     waiting = YES;
134                }
135           }
136      }

原本想講點什麼。。發現確實沒什麼好講的,無非就是判斷應該讀取的長度,而後調用: 

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

socket中獲得讀取的實際長度。

惟一須要講一下的多是數據流向的問題,這裏調用:

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

來判斷數據是否先流向prebuffer,仍是直接流向currentRead,而SSL的讀取中也有相似方法: 

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

這個方法核心的思路就是,若是當前讀取包,長度給明瞭,則直接流向currentRead,若是數據長度不清楚,那麼則去判斷這一次讀取的長度,和currentRead可用空間長度去對比,若是長度比currentRead可用空間小,則流向currentRead,不然先用prebuffer來緩衝。

至於細節方面,你們對着github中的源碼註釋看看吧,這麼大篇幅的業務代碼,一行行講確實沒什麼意義。

走完這兩步讀取,接着就是第三步:

 step3:判斷數據包完成程度:

 

這裏有3種狀況:

1.數據包恰好讀完;2.數據粘包;3.數據斷包;
注:這裏判斷粘包斷包的長度,都是咱們一開始調用read方法給的長度或者分界符得出的。

很顯然,第一種就什麼都不用處理,完美匹配。
第二種狀況,咱們把須要的長度放到currentRead,多餘的長度放到prebuffer中去。
第三種狀況,數據還沒讀完,咱們暫時爲未讀完。

這裏就不貼代碼了。

就這樣普通讀取數據的整個流程就走完了,而SSL的兩種模式,和上述基本一致。

咱們接着根據以前讀取的結果,來判斷數據是否讀完:

//檢查是否讀完
if (done)
{
    //完成此次數據的讀取
    [self completeCurrentRead];
    //若是沒出錯,沒有到邊界,prebuffer中還有可讀數據
    if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
    {
        //讓讀操做離隊,繼續進行下一次讀取
        [self maybeDequeueRead];
    }
}

若是讀完,則去作讀完的操做,而且進行下一次讀取。

咱們來看看讀完的操做:

 1 //完成了此次的讀數據
 2 - (void)completeCurrentRead
 3 {
 4     LogTrace();
 5     //斷言currentRead
 6     NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
 7 
 8     //結果數據
 9     NSData *result = nil;
10 
11     //若是是咱們本身建立的Buffer
12     if (currentRead->bufferOwner)
13     {
14         // We created the buffer on behalf of the user.
15         // Trim our buffer to be the proper size.
16         //修剪buffer到合適的大小
17         //把大小設置到咱們讀取到的大小
18         [currentRead->buffer setLength:currentRead->bytesDone];
19         //賦值給result
20         result = currentRead->buffer;
21     }
22     else
23     {
24         // We did NOT create the buffer.
25         // The buffer is owned by the caller.
26         // Only trim the buffer if we had to increase its size.
27         //這是調用者的data,咱們只會去加大尺寸
28         if ([currentRead->buffer length] > currentRead->originalBufferLength)
29         {
30             //拿到的讀的size
31             NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
32             //拿到原始尺寸
33             NSUInteger origSize = currentRead->originalBufferLength;
34 
35             //取得最大的
36             NSUInteger buffSize = MAX(readSize, origSize);
37             //把buffer設置爲較大的尺寸
38             [currentRead->buffer setLength:buffSize];
39         }
40         //拿到數據的頭指針
41         uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
42 
43         //reslut爲,從頭指針開始到長度爲寫的長度 freeWhenDone爲YES,建立完就釋放buffer
44         result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
45     }
46 
47     __strong id theDelegate = delegate;
48 
49 #pragma mark -總算到調用代理方法,接受到數據了
50     if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
51     {
52         //拿到當前的數據包
53         GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
54 
55         dispatch_async(delegateQueue, ^{ @autoreleasepool {
56             //把result在代理queue中回調出去。
57             [theDelegate socket:self didReadData:result withTag:theRead->tag];
58         }});
59     }
60     //取消掉讀取超時
61     [self endCurrentRead];
62 }

這裏對currentReaddata作了個長度的設置。而後調用代理把最終包給回調出去。最後關掉咱們以前提到的讀取超時。

仍是回到doReadData,就剩下最後一點處理了:

//若是此次讀的數量大於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;

    //若是響應讀數據進度的代理
    if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
    {
        long theReadTag = currentRead->tag;

        //代理queue中回調出去
        dispatch_async(delegateQueue, ^{ @autoreleasepool {

            [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
        }});
    }
}

這裏未完成,若是此次讀取大於0,若是響應讀取進度的代理,則把當前進度回調出去。

最後檢查錯誤:

 1 //檢查錯誤
 2 if (error)
 3 {
 4     //若是有錯直接報錯斷開鏈接
 5     [self closeWithError:error];
 6 }
 7 //若是是讀到邊界錯誤
 8 else if (socketEOF)
 9 {
10     [self doReadEOF];
11 }
12 
13 //若是是等待
14 else if (waiting)
15 {
16     //若是用的是CFStream,則讀取數據和source無關
17     //非CFStream形式
18     if (![self usingCFStreamForTLS])
19     {
20         // Monitor the socket for readability (if we're not already doing so)
21         //從新恢復source
22         [self resumeReadSource];
23     }
24 }

若是有錯,直接斷開socket,若是是邊界錯誤,調用邊界錯誤處理,若是是等待,說明當前包還沒讀完,若是非CFStreamTLS,則恢復source,等待下一次數據到達的觸發。

關於這個讀取邊界錯誤EOF,這裏我簡單的提下,其實它就是服務端發出一個邊界錯誤,說明不會再有數據發送給咱們了。咱們講沒法再接收到數據,可是咱們其實仍是能夠寫數據,發送給服務端的。

doReadEOF這個方法的處理,就是作了這麼一件事。判斷咱們是否須要這種不可讀,只能寫的鏈接。

咱們來簡單看看這個方法:

Part6.讀取邊界錯誤處理:

  1 //讀到EOFException,邊界錯誤
  2 - (void)doReadEOF
  3 {
  4     LogTrace();
  5    //這個方法可能被調用不少次,若是讀到EOF的時候,還有數據在prebuffer中,在調用doReadData以後?? 這個方法可能被持續的調用
  6 
  7     //標記爲讀EOF
  8     flags |= kSocketHasReadEOF;
  9 
 10     //若是是安全socket
 11     if (flags & kSocketSecure)
 12     {
 13         //去刷新sslbuffer中的數據
 14         [self flushSSLBuffers];
 15     }
 16 
 17     //標記是否應該斷開鏈接
 18     BOOL shouldDisconnect = NO;
 19     NSError *error = nil;
 20 
 21     //若是狀態爲開始讀寫TLS
 22     if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
 23     {
 24         //咱們獲得EOF在開啓TLS以前,這個TLS握手是不可能的,所以這是不可恢復的錯誤
 25 
 26         //標記斷開鏈接
 27         shouldDisconnect = YES;
 28         //若是是安全的TLS,賦值錯誤
 29         if ([self usingSecureTransportForTLS])
 30         {
 31             error = [self sslError:errSSLClosedAbort];
 32         }
 33     }
 34     //若是是讀流關閉狀態
 35     else if (flags & kReadStreamClosed)
 36     {
 37 
 38         //不該該被關閉
 39         shouldDisconnect = NO;
 40     }
 41     else if ([preBuffer availableBytes] > 0)
 42     {
 43         //仍然有數據可讀的時候不關閉
 44         shouldDisconnect = NO;
 45     }
 46     else if (config & kAllowHalfDuplexConnection)
 47     {
 48 
 49         //拿到socket
 50         int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
 51 
 52         //輪詢用的結構體
 53 
 54         /*
 55          struct pollfd {
 56          int fd;        //文件描述符
 57          short events;  //要求查詢的事件掩碼  監聽的
 58          short revents; //返回的事件掩碼   實際發生的
 59          };
 60          */
 61 
 62         struct pollfd pfd[1];
 63         pfd[0].fd = socketFD;
 64         //寫數據不會致使阻塞。
 65         pfd[0].events = POLLOUT;
 66         //這個爲當前實際發生的事情
 67         pfd[0].revents = 0;
 68 
 69         /*
 70          poll函數使用pollfd類型的結構來監控一組文件句柄,ufds是要監控的文件句柄集合,nfds是監控的文件句柄數量,timeout是等待的毫秒數,這段時間內不管I/O是否準備好,poll都會返回。timeout爲負數表示無線等待,timeout爲0表示調用後當即返回。執行結果:爲0表示超時前沒有任何事件發生;-1表示失敗;成功則返回結構體中revents不爲0的文件描述符個數。pollfd結構監控的事件類型以下:
 71          int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
 72          */
 73         //阻塞的,可是timeout爲0,則不阻塞,直接返回
 74         poll(pfd, 1, 0);
 75 
 76         //若是被觸發的事件是寫數據
 77         if (pfd[0].revents & POLLOUT)
 78         {
 79             // Socket appears to still be writeable
 80 
 81             //則標記爲不關閉
 82             shouldDisconnect = NO;
 83             //標記爲讀流關閉
 84             flags |= kReadStreamClosed;
 85 
 86             // Notify the delegate that we're going half-duplex
 87             //通知代理,咱們開始半雙工
 88             __strong id theDelegate = delegate;
 89 
 90             //調用已經關閉讀流的代理方法
 91             if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
 92             {
 93                 dispatch_async(delegateQueue, ^{ @autoreleasepool {
 94 
 95                     [theDelegate socketDidCloseReadStream:self];
 96                 }});
 97             }
 98         }
 99         else
100         {
101             //標記爲斷開
102             shouldDisconnect = YES;
103         }
104     }
105     else
106     {
107         shouldDisconnect = YES;
108     }
109 
110     //若是應該斷開
111     if (shouldDisconnect)
112     {
113         if (error == nil)
114         {
115             //判斷是不是安全TLS傳輸
116             if ([self usingSecureTransportForTLS])
117             {
118                 ///標記錯誤信息
119                 if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
120                 {
121                     error = [self sslError:sslErrCode];
122                 }
123                 else
124                 {
125                     error = [self connectionClosedError];
126                 }
127             }
128             else
129             {
130                 error = [self connectionClosedError];
131             }
132         }
133         //關閉socket
134         [self closeWithError:error];
135     }
136     //不斷開
137     else
138     {
139         //若是不是用CFStream流
140         if (![self usingCFStreamForTLS])
141         {
142             // Suspend the read source (if needed)
143             //掛起讀source
144             [self suspendReadSource];
145         }
146     }
147 }

簡單說一下,這個方法主要是對socket是否須要主動關閉進行了判斷:這裏僅僅如下3種狀況,不會關閉socket

  1. 讀流已是關閉狀態(若是加了這個標記,說明爲半雙工鏈接狀態)。
  2. preBuffer中還有可讀數據,咱們須要等數據讀完才能關閉鏈接。
  3. 配置標記爲kAllowHalfDuplexConnection,咱們則要開始半雙工處理。咱們調用了:
  4. poll(pfd, 1, 0);
    函數,若是觸發了寫事件POLLOUT,說明咱們半雙工鏈接成功,則咱們能夠在讀流關閉的狀態下,仍然能夠向服務器寫數據。

其餘狀況下,一概直接關閉socket
而不關閉的狀況下,咱們會掛起source。這樣咱們就只能可寫不可讀了。

最後仍是提下SSL的回調方法,數據解密的地方。兩種模式的回調;

Part7.兩種SSL數據解密位置:

1.CFStream:當咱們調用:

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

數據就會被解密。
2.SSL安全通道:當咱們調用:

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

會觸發SSL綁定的函數回調:

//讀函數
static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
{
    //拿到socket
    GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;

    //斷言當前爲socketQueue
    NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");

    //讀取數據,而且返回狀態碼
    return [asyncSocket sslReadWithBuffer:data length:dataLength];
}

接着咱們在下面的方法進行了數據讀取:

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

其實read這一步,數據是沒有被解密的,而後傳遞迴SSLReadFunction,在傳遞到SSLRead內部,數據被解密。

尾聲:

這個系列就剩下最後一篇Write了。因爲內容相對比較簡單,預計就一篇寫完了。
若是一直看到這裏的朋友,會發現,相對以前有些內容,講解沒那麼詳細了。其實緣由主要有兩點,一是代碼數量龐大,確實沒法詳細。二是樓主對這個系列寫的有點不耐煩,想要儘快結束了..
不過至少整篇的源碼註釋在github上是有的,我以爲你們本身去對着源碼去閱讀理解一樣重要,若是一直逐字逐行的去講,那就真的沒什麼意義了。

相關文章
相關標籤/搜索