接上篇:iOS即時通信之CocoaAsyncSocket源碼解析四 原文html
本文爲CocoaAsyncSocket
Read篇終,將重點涉及該框架是如何利用緩衝區對數據進行讀取、以及各類狀況下的數據包處理,其中還包括普通的、和基於TLS
的不一樣讀取操做等等。git
前文講完了兩次TLS
創建鏈接的流程,接着就是本篇的重頭戲了:doReadData
方法。在這裏我不許備直接把這個整個方法列出來,由於就光這一個方法,加上註釋有1200行,整個貼過來也沒法展開描述,因此在這裏我打算對它分段進行講解:github
注:如下代碼整個包括在doReadData
大括號中:數組
//讀取數據 - (void)doReadData { .... }
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
內核來實現的,並不須要藉助於runloop
。app
這裏當超時時間間隔到達時,咱們會執行超時操做:框架
[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]; }
按理說,咱們有當前讀取包的時候,在去從prebuffer
、socket
中去讀取,可是這裏爲何要提早去讀呢?
咱們來看看這個框架做者的解釋:
// 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
,讀取數據的時候須要解密的過程,而這個過程是費時的,咱們不必讓用戶在讀取數據的時候去等待這個解密的過程,咱們能夠提早在數據一到達,就去讀取解密。
並且這種方式,還能時刻根據TLS
的goodbye
包來準確的檢測到TCP
斷開鏈接。
在咱們來看flushSSLBuffers
方法以前,咱們先來看看這個一直提到的全局緩衝區prebuffer
的定義,它其實就是下面這麼一個類的實例:
GCDAsyncSocketPreBuffer
的定義@interface GCDAsyncSocketPreBuffer : NSObject { //unsigned char //提早的指針,指向這塊提早的緩衝區 uint8_t *preBuffer; //size_t 它是一個與機器相關的unsigned類型,其大小足以保證存儲內存中對象的大小。 //它能夠存儲在理論上是可能的任何類型的數組的最大大小 size_t preBufferSize; //讀的指針 uint8_t *readPointer; //寫的指針 uint8_t *writePointer; }
裏面存了3個指針,包括preBuffer起點指針、當前讀寫所處位置指針、以及一個preBufferSize
,這個size
爲preBuffer
所指向的位置,在內存中分配的空間大小。
咱們來看看它的幾個方法:
//初始化 - (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數據讀完,則重置讀寫指針的位置,仍是指向初始化位置。
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
的數據處理:
CFStream
類型:咱們會調用下面這個函數去從stream
而且讀取數據並解密:CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
數據被讀取到後,直接轉移到了prebuffer中,而且調用:
[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
後續數據讀取處理基本類似,因此如今暫時不提。
SSL
類型的數據,那麼先解密了,緩衝到prebuffer
中去。socket
可讀數據大於0,非CFStream
SSL類型,則掛起source,防止反覆觸發。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讀取到某個標記符爲止。
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
不然就等待着,直到當前數據包任務完成。
先從prebuffer
中去讀取,若是讀完了,當前數據包任務仍未完成,那麼再從socket
中去讀取。
而判斷包是否讀完,都是用咱們上面的3種類型,來對應處理的。
講了半天理論,想必你們看的有點不耐煩了,接下來看看代碼實際是如何處理的吧:
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
數據包中。
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
中的源碼註釋看看吧,這麼大篇幅的業務代碼,一行行講確實沒什麼意義。
走完這兩步讀取,接着就是第三步:
這裏有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 }
這裏對currentRead
的data
作了個長度的設置。而後調用代理把最終包給回調出去。最後關掉咱們以前提到的讀取超時。
仍是回到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
,若是是邊界錯誤,調用邊界錯誤處理,若是是等待,說明當前包還沒讀完,若是非CFStream
的TLS
,則恢復source
,等待下一次數據到達的觸發。
關於這個讀取邊界錯誤EOF
,這裏我簡單的提下,其實它就是服務端發出一個邊界錯誤,說明不會再有數據發送給咱們了。咱們講沒法再接收到數據,可是咱們其實仍是能夠寫數據,發送給服務端的。
而doReadEOF
這個方法的處理,就是作了這麼一件事。判斷咱們是否須要這種不可讀,只能寫的鏈接。
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
:
preBuffer
中還有可讀數據,咱們須要等數據讀完才能關閉鏈接。kAllowHalfDuplexConnection
,咱們則要開始半雙工處理。咱們調用了:poll(pfd, 1, 0);
POLLOUT
,說明咱們半雙工鏈接成功,則咱們能夠在讀流關閉的狀態下,仍然能夠向服務器寫數據。其餘狀況下,一概直接關閉socket
。
而不關閉的狀況下,咱們會掛起source
。這樣咱們就只能可寫不可讀了。
最後仍是提下SSL
的回調方法,數據解密的地方。兩種模式的回調;
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
上是有的,我以爲你們本身去對着源碼去閱讀理解一樣重要,若是一直逐字逐行的去講,那就真的沒什麼意義了。