iOS GCDAsyncSocket原始碼分析(二)
阿新 • • 發佈:2019-12-25
由於上一篇文章篇幅過長移到這邊。
3.read&write
- 先看write
//寫資料對外方法
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
if ([data length] == 0) return;
//初始化寫包,可知write操作是用GCDAsyncWritePacket這種包的形式傳送的,如果沒錯的話,read應該也是這樣吧。
GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
//非同步
dispatch_async(socketQueue,^{ @autoreleasepool {
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
//writeQueue是一個數組,把當前packet新增進去
[writeQueue addObject:packet];
//還沒看到write(),得繼續跳轉
[self maybeDequeueWrite];
}
}});
// Do not rely on the block being run in order to release the packet,// as the queue might get released without the block completing.
}
複製程式碼
這個方法只是構建了一個GCDAsyncWritePacket包,新增到writequeue陣列中,然後繼續呼叫別的方法
- (void)maybeDequeueWrite
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),@"Must be dispatched on socketQueue");
// If we're not currently processing a write AND we have an available write stream
//currentWrite也是一個GCDAsyncWritePacket包
//如果currentWrite為空,且socket已經連線
if ((currentWrite == nil) && (flags & kConnected))
{
//如果用來寫的陣列數量大於0
if ([writeQueue count] > 0)
{
// Dequeue the next object in the write queue
//將第一個包賦值給currentWrite,並從陣列中移除
currentWrite = [writeQueue objectAtIndex:0];
[writeQueue removeObjectAtIndex:0];
//TLS,如果是GCDAsyncSpecialPacket,一般不走這裡
if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
{
LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
// Attempt to start TLS
flags |= kStartingWriteTLS;
// This method won' t do anything unless both kStartingReadTLS and kStartingWriteTLS are set
[self maybeStartTLS];
}
else
{
LogVerbose(@"Dequeued GCDAsyncWritePacket");
// Setup write timer (if needed)
//設定一個GCD timer 來記錄超時時間
[self setupWriteTimerWithTimeout:currentWrite->timeout];
// 開始write
[self doWriteData];
}
}
// flags & kDisconnectAfterWrites 跟 陣列小於等於0 的 意思差不多
else if (flags & kDisconnectAfterWrites)
{
//如果沒有可讀任務,直接關閉socket
if (flags & kDisconnectAfterReads)
{
if (([readQueue count] == 0) && (currentRead == nil))
{
[self closeWithError:nil];
}
}
else
{
[self closeWithError:nil];
}
}
}
}
複製程式碼
這個方法主要的就是給currentWrite賦值,writeQueue的數量判斷,大於0就繼續往下,小於等於0就斷開連線。還做了一步[GCDAsyncSpecialPacket class]的判斷,我們write的時候,進來的是普通包,不是special包,基本上不會走進去,所以我那繼續往下走就是執行[self doWriteData];
這又是一個很長的方法。這裡麵包含了很多flags判斷操作,比如:socket安全等等,需要有設定才會執行
- (void)doWriteData
{
LogTrace();
//currentWrite為空,不寫
if ((currentWrite == nil) || (flags & kWritesPaused))
{
LogVerbose(@"No currentWrite or kWritesPaused");
if ([self usingCFStreamForTLS])
{
//啥也不幹
}
else
{
//如果socket中可接受寫資料,防止反覆觸發寫source,掛起
if (flags & kSocketCanAcceptBytes)
{
[self suspendWriteSource];
}
}
return;
}
//如果當前socket無法在寫資料了
if (!(flags & kSocketCanAcceptBytes))
{
LogVerbose(@"No space available to write...");
// No space available to write.
//如果不是cfstream
if (![self usingCFStreamForTLS])
{
// Need to wait for writeSource to fire and notify us of
// available space in the socket's internal write buffer.
//則恢復寫source,當有空間去寫的時候,會觸發回來
[self resumeWriteSource];
}
return;
}
//如果正在進行TLS認證,就是那個specialpacket,我們當前不是這個所以先跳過
if (flags & kStartingWriteTLS)
{
LogVerbose(@"Waiting for SSL/TLS handshake to complete");
// The writeQueue is waiting for SSL/TLS handshake to complete.
if (flags & kStartingReadTLS)
{
//如果是安全通道,並且I/O阻塞,那麼重新去握手
if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
{
// We are in the process of a SSL Handshake.
// We were waiting for available space in the socket's internal OS buffer to continue writing.
[self ssl_continueSSLHandshake];
}
}
//說明不走`TLS`了,因為只支援寫的TLS
else
{
// We are still waiting for the readQueue to drain and start the SSL/TLS process.
// We now know we can write to the socket.
//掛起寫source
if (![self usingCFStreamForTLS])
{
// Suspend the write source or else it will continue to fire nonstop.
[self suspendWriteSource];
}
}
return;
}
// Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
//開始寫資料
BOOL waiting = NO;
NSError *error = nil;
size_t bytesWritten = 0;
//安全連線
if (flags & kSocketSecure)
{
//這裡先省略,關鍵看普通連線。有需要仔細瞭解的可以私信我。我給你發程式碼- -
...
}
//普通socket
else
{
//拿到當前socket
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
//得到指標偏移
const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
// 要寫的資料大小
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
{
bytesToWrite = SIZE_MAX;
}
//直接寫,熟悉的write()
ssize_t result = write(socketFD,buffer,(size_t)bytesToWrite);
LogVerbose(@"wrote to socket = %zd",result);
// 結果判斷
if (result < 0)
{
//IO阻塞
if (errno == EWOULDBLOCK)
{
waiting = YES;
}
else
{
error = [self errnoErrorWithReason:@"Error in write() function"];
}
}
else
{
//得到寫的大小
bytesWritten = result;
}
}
//注意,如果用CFStream,很可能會被惡意的放置資料 阻塞socket
//如果等待,則恢復寫source
if (waiting)
{
//把socket可接受資料的標記去掉
flags &= ~kSocketCanAcceptBytes;
if (![self usingCFStreamForTLS])
{
//恢復寫source
[self resumeWriteSource];
}
}
// Check our results
//判斷是否完成
BOOL done = NO;
//判斷已寫大小
if (bytesWritten > 0)
{
// Update total amount read for the current write
//更新當前總共寫的大小
currentWrite->bytesDone += bytesWritten;
LogVerbose(@"currentWrite->bytesDone = %lu",(unsigned long)currentWrite->bytesDone);
// Is packet done?
//判斷當前寫包是否寫完
done = (currentWrite->bytesDone == [currentWrite->buffer length]);
}
//如果完成了
if (done)
{
//完成操作,回調發送成功代理事件,注意,代理回撥是這裡
[self completeCurrentWrite];
if (!error)
{
dispatch_async(socketQueue,^{ @autoreleasepool{
//開始下一次的讀取任務
[self maybeDequeueWrite];
}});
}
}
//未完成
else
{
//如果不是等待 而且沒有出錯
if (!waiting && !error)
{
//這是我們寫了一部分資料的情況。
//去掉可接受資料的標記
flags &= ~kSocketCanAcceptBytes;
//再去等讀source觸發
if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
}
//如果已寫大於0
if (bytesWritten > 0)
{
__strong id theDelegate = delegate;
//呼叫寫的進度代理
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
{
long theWriteTag = currentWrite->tag;
dispatch_async(delegateQueue,^{ @autoreleasepool {
[theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
}});
}
}
}
// Check for errors
//如果有錯,則報錯斷開連線
if (error)
{
[self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]];
}
}
複製程式碼
這個方法賊長,長的主要原因還是TSL,SSL這兩個安全傳輸協議的處理,我們這裡就先看看普通的吧...那些實在很長,註釋也很多。 接下來看read,看看會發現,方法格式差不多
- read
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag
{
if (offset > [buffer length]) {
LogWarn(@"Cannot read: offset > [buffer length]");
return;
}
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:length
timeout:timeout
readLength:0
terminator:nil
tag:tag];
dispatch_async(socketQueue,^{ @autoreleasepool {
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
//往讀的佇列新增任務,任務是包的形式
[readQueue addObject:packet];
[self maybeDequeueRead];
}
}});
}
複製程式碼
同樣是構造一個GCDAsyncReadPacket包,然後新增到陣列中,然後執行下一個方法
- (void)maybeDequeueRead
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),@"Must be dispatched on socketQueue");
//如果當前讀的包為空,而且flag為已連線
if ((currentRead == nil) && (flags & kConnected))
{
//如果讀的queue大於0 (裡面裝的是我們封裝的GCDAsyncReadPacket資料包)
if ([readQueue count] > 0)
{
// Dequeue the next object in the write queue
//使得下一個物件從寫的queue中離開
//從readQueue中拿到第一個寫的資料
currentRead = [readQueue objectAtIndex:0];
//移除
[readQueue removeObjectAtIndex:0];
//我們的資料包,如果是GCDAsyncSpecialPacket這種型別,這個包裡裝了TLS的一些設定
//如果是這種型別的資料,那麼我們就進行TLS
if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
{
LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
// Attempt to start TLS
//標記flag為正在讀取TLS
flags |= kStartingReadTLS;
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
//只有讀寫都開啟了TLS,才會做TLS認證
[self maybeStartTLS];
}
else
{
LogVerbose(@"Dequeued GCDAsyncReadPacket");
// Setup read timer (if needed)
//設定讀的任務超時,每次延時的時候還會呼叫 [self doReadData];
[self setupReadTimerWithTimeout:currentRead->timeout];
// Immediately read,if possible
//讀取資料
[self doReadData];
}
}
//讀的佇列沒有資料,標記flag為,讀了沒有資料則斷開連線狀態
else if (flags & kDisconnectAfterReads)
{
//如果標記有寫然後斷開連線
if (flags & kDisconnectAfterWrites)
{
//如果寫的佇列為0,而且寫為空
if (([writeQueue count] == 0) && (currentWrite == nil))
{
//斷開連線
[self closeWithError:nil];
}
}
else
{
//斷開連線
[self closeWithError:nil];
}
}
//如果有安全socket。
else if (flags & kSocketSecure)
{
[self flushSSLBuffers];
//如果可讀位元組數為0
if ([preBuffer availableBytes] == 0)
{
//CFStream形式TLS
if ([self usingCFStreamForTLS]) {
// Callbacks never disabled
}
else {
//重新恢復讀的source。因為每次開始讀資料的時候,都會掛起讀的source
[self resumeReadSource];
}
}
}
}
}
複製程式碼
也是一系列的安全協議判斷並最終走向[self doReadData];
這個方法比write還長,我覺得有必要列一下這個方法裡面都做了什麼。。。
1.判斷currentRead是否為空,為空就掛起readSource 2.判斷TLS 3.從preBuffer區讀取資料 4.從socket中讀取資料 5.讀取完代理回撥 6.錯誤檢查
- (void)doReadData
{
LogTrace();
//如果當前讀取的包為空,或者flag為讀取停止,這兩種情況是不能去讀取資料的
if ((currentRead == nil) || (flags & kReadsPaused))
{
LogVerbose(@"No currentRead or kReadsPaused");
// Unable to read at this time
//如果是安全的通訊,通過TLS/SSL
if (flags & kSocketSecure)
{
// 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.
// 當用戶開始進行一個read,這些加密的資料需要被解碼
// 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.
//另外的理由是,我們做這些不得不去檢測socket的斷開連線
// The SSL/TLS protocol has it's own disconnection handshake.
//SSL/TLS協議有自己的斷開連線的握手
// So when a secure socket is closed,a "goodbye" packet comes across the wire.
//所以當一個安全連線關閉,一個“goodbye"資料包會被髮送在電報中
// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
//我們想要確保讀到“goodbye”資料包,因此我們可以確定檢測到TCP連線斷開
//重新整理SSLBuffer,把資料從鏈路上移到prebuffer中 (當前暫停的時候做)
[self flushSSLBuffers];
}
//判斷是否用的是 CFStream的TLS
if ([self usingCFStreamForTLS])
{
// CFReadStream only fires once when there is available data.
// It won't fire again until we've invoked CFReadStreamRead.
//CFReadStream只會調起一次,當有可讀的資料。 不會再次被呼叫,直到我們喚醒CFReadStreamRead。
// source --> data --> stream
}
else
{
// If the readSource is firing,we need to pause it
// or else it will continue to fire over and over again.
//
// If the readSource is not firing,// we want it to continue monitoring the socket.
//如果讀的source正在觸發,我們需要去停止它,否則它會持續的被觸發一遍又一遍。(要等我們把現有傳過來的資料讀完,才能觸發下一次。)
//如果讀的source沒有觸發。我們想要它繼續去監視socket.
//掛起source
if (socketFDBytesAvailable > 0)
{
[self suspendReadSource];
}
}
return;
}
//當前資料包不為空或者flag不為kReadsPaused,正式開始讀取資料
//宣告是否可讀,可讀資料為多大
BOOL hasBytesAvailable = NO;
unsigned long estimatedBytesAvailable = 0;
//如果用了CFStream
if ([self usingCFStreamForTLS])
{
#if TARGET_OS_IPHONE
// Requested CFStream,rather than SecureTransport,for TLS (via GCDAsyncSocketUseCFStreamForTLS)
//不需要得到資料大小
estimatedBytesAvailable = 0;
//判斷如果狀態可讀而且有可讀資料,hasBytesAvailable則為YES
if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
hasBytesAvailable = YES;
else
hasBytesAvailable = NO;
#endif
}
else
{
//拿到當前讀到的資料大小,安全通道的和普通socket資料都和 socketFDBytesAvailable 有關
estimatedBytesAvailable = socketFDBytesAvailable;
//如果是安全socket
if (flags & kSocketSecure)
{
// There are 2 buffers to be aware of here.
// 這裡有2個buffer需要知道,一個是sslPreBuffer還有一個是安全傳輸中未讀取的buffer
// We are using SecureTransport,a TLS/SSL security layer which sits atop TCP.
//我們使用了安全的傳輸,一個TLS/SSL在TCP上
// We issue a read to the SecureTranport API,which in turn issues a read to our SSLReadFunction.
//我們發出read在安全傳輸的API上,其實就是發出read在SSLReadFunction上
// Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
//我們SSLReadFunction 從BSD socket去讀,並且返回加密的資料到安全傳輸中。
// SecureTransport then decrypts the data,and finally returns the decrypted data back to us.
// 然後安全傳輸返回解密的資料,最終把解密的資料返回給我們
// The first buffer is one we create.
//第一個buffe是我們建立的
// SecureTransport often requests small amounts of data.
//安全的傳輸經常需要少量的資料
// This has to do with the encypted packets that are coming across the TCP stream.
//他們不得不用加密包來穿過TCP流
// But it's non-optimal to do a bunch of small reads from the BSD socket.
//但是,這是不是最佳的,從BSD Socket上,進行一堆小的閱讀
// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
//所以我們SSLReadFunction從socket中讀取所有提供的資料(最佳的方式)
// and may store excess in the sslPreBuffer.
//可能在sslPreBuffer中儲存超出的部分
//預估的讀取大小再加上 ssl中可讀的
estimatedBytesAvailable += [sslPreBuffer availableBytes];
// The second buffer is within SecureTransport.
//第二個Buffer在安全傳輸中
// As mentioned earlier,there are encrypted packets coming across the TCP stream.
//像之前提到的,這裡有加密的包在TCP流中
// SecureTransport needs the entire packet to decrypt it.
//安全傳輸需要把整個包解密
// But if the entire packet produces X bytes of decrypted data,//但是如果整個包只有 X位元組是加密的資料
// and we only asked SecureTransport for X/2 bytes of data,//而我們僅僅訪問了 SecureTransport中一半位元組的資料
// it must store the extra X/2 bytes of decrypted data for the next read.
// 我們必須儲存另一半在下一次讀取中
// The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
//SSLGetBufferedReadSize方法,將告訴我們內部的buffer大小
// From the documentation:
//
// "This function does not block or cause any low-level read operations to occur."
//從文件中:這個方法不會阻塞和引起低級別的讀取操作發生
size_t sslInternalBufSize = 0;
//拿到SSL上下文中的大小,也就是計算我們能從SSLReead中能獲取到的資料大小
SSLGetBufferedReadSize(sslContext,&sslInternalBufSize);
//加到預估大小中
estimatedBytesAvailable += sslInternalBufSize;
}
//如果 estimatedBytesAvailable 大於0 為YES
hasBytesAvailable = (estimatedBytesAvailable > 0);
}
//如果沒有資料可讀 -- 一次傳完的包
if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
{
LogVerbose(@"No data available to read...");
//而且不是用CFStream
if (![self usingCFStreamForTLS])
{
// Need to wait for readSource to fire and notify us of
// available data in the socket's internal read buffer.
//恢復讀的source
[self resumeReadSource];
}
return;
}
//如果開始 kStartingReadTLS,說明正在準備握手,那麼我們不能進行讀取操作,要直接返回
if (flags & kStartingReadTLS)
{
LogVerbose(@"Waiting for SSL/TLS handshake to complete");
// The readQueue is waiting for SSL/TLS handshake to complete.
//如果正在寫的TLS,如果上一次是阻塞錯誤,那麼在重新去握手,(防止一次握手阻塞而失敗導致不再握手)
if (flags & kStartingWriteTLS)
{
//如果用的是非CFStreamTLS,即安全的TLS 而且上一次握手錯誤為 IO阻塞的
if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
{
// We are in the process of a SSL Handshake.
// We were waiting for incoming data which has just arrived.
//SSL的握手
[self ssl_continueSSLHandshake];
}
}
else
{
// We are still waiting for the writeQueue to drain and start the SSL/TLS process.
// We now know data is available to read.
//如果當前不是CFStream的方式
if (![self usingCFStreamForTLS])
{
// Suspend the read source or else it will continue to fire nonstop.
//掛起讀的queue
[self suspendReadSource];
}
}
return;
}
//是否完成讀的操作
BOOL done = NO; // Completed read operation
//錯誤
NSError *error = nil; // Error occurred
//當前總讀的資料量
NSUInteger totalBytesReadForCurrentRead = 0;
//
// STEP 1 - READ FROM PREBUFFER
//
//先從提前緩衝區去讀,如果緩衝區可讀大小大於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、讀取到一個明確的界限
NSUInteger bytesToCopy;
//如果當前讀的資料界限不為空
if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
//直接讀到界限
bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
}
else
{
// Read type #1 or #2
//讀取資料,讀到指定長度或者資料包的長度為止
bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
}
// Make sure we have enough room in the buffer for our read.
//從上兩步拿到我們需要讀的長度,去看看有沒有空間去儲存
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
// Copy bytes from prebuffer into packet buffer
//拿到我們需要追加資料的指標位置
//當前讀的資料 + 開始偏移 + 已經讀完的??
uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
currentRead->bytesDone;
//從prebuffer處複製過來資料,bytesToCopy長度
memcpy(buffer,[preBuffer readBuffer],bytesToCopy);
// Remove the copied bytes from the preBuffer
//從preBuffer移除掉已經複製的資料
[preBuffer didRead:bytesToCopy];
LogVerbose(@"copied(%lu) preBufferLength(%zu)",(unsigned long)bytesToCopy,[preBuffer availableBytes]);
// Update totals
//已讀的資料加上
currentRead->bytesDone += bytesToCopy;
//當前已讀的資料加上
totalBytesReadForCurrentRead += bytesToCopy;
// Check to see if the read operation is done
//判斷是不是讀完了
if (currentRead->readLength > 0)
{
// Read type #2 - read a specific length of data
//如果已讀 == 需要讀的長度,說明已經讀完
done = (currentRead->bytesDone == currentRead->readLength);
}
//判斷界限標記
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?
//如果已讀的大小大於最大的大小,則報溢位錯誤
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));
}
}
//
// STEP 2 - READ FROM SOCKET
// 從socket中去讀取
//是否讀到EOFException ,這個錯誤指的是檔案結尾了還在繼續讀,就會導致這個錯誤被丟擲
BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file)
//如果沒完成,且沒錯,沒讀到結尾,且沒有可讀資料了
BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data,waiting for more
//如果沒完成,且沒錯,沒讀到結尾,有可讀資料
if (!done && !error && !socketEOF && hasBytesAvailable)
{
//斷言,有可讀資料
NSAssert(([preBuffer availableBytes] == 0),@"Invalid logic");
//是否讀到preBuffer中去
BOOL readIntoPreBuffer = NO;
uint8_t *buffer = NULL;
size_t bytesRead = 0;
//如果flag標記為安全socket
if (flags & kSocketSecure)
{
//如果使用CFStream
if ([self usingCFStreamForTLS])
{
#if TARGET_OS_IPHONE
// Using CFStream,for TLS
//預設讀的大小32KB
NSUInteger defaultReadLength = (1024 * 32);
//決定我們讀的位元組大小,和是否使用prebuffer
NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
shouldPreBuffer:&readIntoPreBuffer];
// 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.
//如果使用preBuffer,則去確保有這麼大的空間來存
if (readIntoPreBuffer)
{
[preBuffer ensureCapacityForWrite:bytesToRead];
//拿到寫的buffer
buffer = [preBuffer writeBuffer];
}
//不用prebuffer
else
{
//確保大小,其實不用。。
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
//獲取到當前buffer上次寫到的偏移位置
buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ currentRead->startOffset
+ currentRead->bytesDone;
}
// Read data into buffer
#pragma mark - 開始讀取資料 CFStream
//從readStream中讀取資料,到buffer中
CFIndex result = CFReadStreamRead(readStream,(CFIndex)bytesToRead);
LogVerbose(@"CFReadStreamRead(): result = %i",(int)result);
//讀取失敗
if (result < 0)
{
error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
}
// 讀取丟擲了EOFException,到資料邊界了
else if (result == 0)
{
socketEOF = YES;
}
//正常讀取
else
{
waiting = YES;
bytesRead = (size_t)result;
}
// We only know how many decrypted bytes were read.
// The actual number of bytes read was likely more due to the overhead of the encryption.
// So we reset our flag,and rely on the next callback to alert us of more data.
//移除仍然有資料可讀的標記
flags &= ~kSecureSocketHasBytesAvailable;
#endif
}
else
{
//用安全傳輸來
// Using SecureTransport for TLS
//
// We know:
// - how many bytes are available on the socket
// - how many encrypted bytes are sitting in the sslPreBuffer
// - how many decypted bytes are sitting in the sslContext
//
// But we do NOT know:
// - how many encypted bytes are sitting in the sslContext
//
// So we play the regular game of using an upper bound instead.
//也是預設32KB
NSUInteger defaultReadLength = (1024 * 32);
//如果預設大小小於預估的大小,則讓預設大小的 = 預估大小 + 16KB ,16KB幹嘛用的??
if (defaultReadLength < estimatedBytesAvailable) {
defaultReadLength = estimatedBytesAvailable + (1024 * 16);
}
//去要讀的大小,還有是否走Prebuffer
NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
shouldPreBuffer:&readIntoPreBuffer];
//如果要讀的大小大於最大值 ,則讓其等於最大值
if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t
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;
}
// The documentation from Apple states:
//
// "a read operation might return errSSLWouldBlock,// indicating that less data than requested was actually transferred"
//
// However,starting around 10.7,the function will sometimes return noErr,// even if it didn't read as much data as requested. So we need to watch out for that.
OSStatus result;
#pragma mark - 開始讀取資料 SSLRead
//迴圈去讀
do
{
//拿到當前寫到的buffer位置
//頭指標 + 讀了的大小
void *loop_buffer = buffer + bytesRead;
//得到還需要讀的大小
size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
//設定這一次迴圈讀的進度
size_t loop_bytesRead = 0;
//用ssl方式去讀取資料,頭指標為loop_buffer,大小為loop_bytesToRead,進度為loop_bytesRead
result = SSLRead(sslContext,loop_buffer,loop_bytesToRead,&loop_bytesRead);
LogVerbose(@"read from secure socket = %u",(unsigned)loop_bytesRead);
//讀了的大小加進度
bytesRead += loop_bytesRead;
}
//如果沒出錯,且讀的大小小於需要讀的大小,就一直迴圈
while ((result == noErr) && (bytesRead < bytesToRead));
//如果出錯
if (result != noErr)
{
//如果是IO阻塞的錯誤, waiting
if (result == errSSLWouldBlock)
waiting = YES;
else
{
//如果是SSL連線斷開的錯誤
if (result == errSSLClosedGraceful || result == errSSLClosedAbort)
{
// We've reached the end of the stream.
// Handle this the same way we would an EOF from the socket.
//說明到邊界了
socketEOF = YES;
//把錯誤賦值給SSLErrCode
sslErrCode = result;
}
else
{
//直接拿到SSL資料錯誤
error = [self sslError:result];
}
}
// It's possible that bytesRead > 0,even if the result was errSSLWouldBlock.
//很有可能bytesRead中有資料,即使結果是IO阻塞的錯誤
// This happens when the SSLRead function is able to read some data,// but not the entire amount we requested.
if (bytesRead <= 0)
{
bytesRead = 0;
}
}
//不要修改 socketFDBytesAvailable 可讀資料大小,因為這個會在 SSLReadFunction中被修改
// Do not modify socketFDBytesAvailable.
// It will be updated via the SSLReadFunction().
}
}
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.
//和上面類似,讀取到邊界標記??不是吧
if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
//讀這個長度,如果到maxlength,就用maxlength。看如果可用空間大於需要讀的空間,則不用prebuffer
bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
shouldPreBuffer:&readIntoPreBuffer];
}
else
{
// Read type #1 or #2
//直接讀這個長度,如果到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 - 開始讀取資料,最普通的形式 read
//讀資料
ssize_t result = read(socketFD,(size_t)bytesToRead);
LogVerbose(@"read from socket = %i",(int)result);
//讀取錯誤
if (result < 0)
{
//EWOULDBLOCK IO阻塞
if (errno == EWOULDBLOCK)
//先等待
waiting = YES;
else
//得到錯誤
error = [self errnoErrorWithReason:@"Error in read() function"];
//把可讀取的長度設定為0
socketFDBytesAvailable = 0;
}
//讀到邊界了
else if (result == 0)
{
socketEOF = YES;
socketFDBytesAvailable = 0;
}
//正常
else
{
//設定讀到的資料長度
bytesRead = result;
//如果讀到的資料小於應該讀的長度,說明這個包沒讀完
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比讀了的資料小的話,直接置為0
if (socketFDBytesAvailable <= bytesRead)
socketFDBytesAvailable = 0;
//減去已讀大小
else
socketFDBytesAvailable -= bytesRead;
}
//如果 socketFDBytesAvailable 可讀數量為0,把讀的狀態切換為等待
if (socketFDBytesAvailable == 0)
{
waiting = YES;
}
}
}
//如果這次讀的位元組大於0
if (bytesRead > 0)
{
// Check to see if the read operation is done
//檢查這個包的資料是否讀完,用readLength來讀的
if (currentRead->readLength > 0)
{
// Read type #2 - read a specific length of data
//
// Note: We should never be using a prebuffer when we're reading a specific length of data.
//我們讀取固定大小的時候是永遠不用寫到prebuffer中去的
//斷言,是不需要寫到prebuffer中去的
NSAssert(readIntoPreBuffer == NO,@"Invalid logic");
//加上讀的數量
currentRead->bytesDone += bytesRead;
//把這一次讀的數量加上來
totalBytesReadForCurrentRead += bytesRead;
//判斷是否已讀完
done = (currentRead->bytesDone == currentRead->readLength);
}
//用邊界來讀的
else if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
//如果是往buffer中讀的
if (readIntoPreBuffer)
{
// We just read a big chunk of data into the preBuffer
//移動writeBuffer的指標
[preBuffer didWrite:bytesRead];
LogVerbose(@"read data into preBuffer - preBuffer.length = %zu",[preBuffer availableBytes]);
// Search for the terminating sequence
//拿到需要讀取的大小,根據term,並且判斷是否已讀完
NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
LogVerbose(@"copying %lu bytes from preBuffer",(unsigned long)bytesToCopy);
// Ensure there's room on the read packet's buffer
//確保有這麼大的空間
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
// Copy bytes from prebuffer into read buffer
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
#pragma mark - 把資料從preBuffer中移到currentRead上
memcpy(readBuf,bytesToCopy);
// Remove the copied bytes from the prebuffer
//標記已經讀了這麼多資料
[preBuffer didRead:bytesToCopy];
LogVerbose(@"preBuffer.length = %zu",[preBuffer availableBytes]);
// Update totals
currentRead->bytesDone += bytesToCopy;
totalBytesReadForCurrentRead += bytesToCopy;
// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
}
//沒有用prebuffer
else
{
// We just read a big chunk of data directly into the packet's buffer.
// We need to move any overflow into the prebuffer.
//我們需要把資料流向prebuffer?
//拿到粘包長度,(為溢位長度,溢位的我們要寫到prebuffer中去。給下一個包去讀)
NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
//如果為0,說明完全匹配
if (overflow == 0)
{
//加上這次讀取的位元組數
currentRead->bytesDone += bytesRead;
//總的讀取位元組數
totalBytesReadForCurrentRead += bytesRead;
//標誌讀取完成
done = YES;
}
//說明讀取的資料總長度比當前包大(粘包)
else if (overflow > 0)
{
//當前包內的長度
NSInteger underflow = bytesRead - overflow;
// Copy excess data into preBuffer
LogVerbose(@"copying %ld overflow bytes into preBuffer",(long)overflow);
//確保preBuffer有這麼大的大小
[preBuffer ensureCapacityForWrite:overflow];
//把buffer往後移,去掉重合的資料大小
uint8_t *overflowBuffer = buffer + underflow;
//寫到writeBuffer中,長度為 overflow(非重合部分)
memcpy([preBuffer writeBuffer],overflowBuffer,overflow);
//後移寫指標
[preBuffer didWrite:overflow];
LogVerbose(@"preBuffer.length = %zu",[preBuffer availableBytes]);
// Note: The completeCurrentRead method will trim the buffer for us.
//加上已讀的大小(非粘包的)
currentRead->bytesDone += underflow;
//這次總共讀取的大小
totalBytesReadForCurrentRead += underflow;
//當前讀取完成
done = YES;
}
//資料還沒達到邊界
else
{
// The term was not found within the data that we read.
//已讀的加上 bytesRead
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
//標記為未完成
done = NO;
}
}
//如果未完成 而且當前包的資料包最大長度大於0
if (!done && currentRead->maxLength > 0)
{
// We're not done and there's a set maxLength.
// Have we reached that maxLength yet?
//判斷寫的大小 是否達到包的最大值
if (currentRead->bytesDone >= currentRead->maxLength)
{
//得到讀取溢位的錯誤
error = [self readMaxedOutError];
}
}
}
//沒邊界,沒給定長度(無法判斷當前包結尾)
else
{
// Read type #1 - read all available data
//如果從prebuffer中讀取
if (readIntoPreBuffer)
{
// We just read a chunk of data into the preBuffer
//指標後移
[preBuffer didWrite:bytesRead];
// Now copy the data into the read packet.
//
// Recall that we didn't read directly into the packet's buffer to avoid
// over-allocating memory since we had no clue how much data was available to be read.
//
// Ensure there's room on the read packet's buffer
//確保currentRead中有bytesRead大小可用
[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
// Copy bytes from prebuffer into read buffer
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
//拿到指標賦值
memcpy(readBuf,bytesRead);
// Remove the copied bytes from the prebuffer
//標記讀了這麼多資料
[preBuffer didRead:bytesRead];
// Update totals
//更新已讀
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
//在currentRead中的話直接加就行
else
{
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
//因為無法判斷結尾,所以每次讀都會直接標記為YES,即一個包完成
done = YES;
}
} // if (bytesRead > 0)
} // if (!done && !error && !socketEOF && hasBytesAvailable)
//如果未完成,而且沒有應讀長度和邊界符
if (!done && currentRead->readLength == 0 && currentRead->term == nil)
{
// Read type #1 - read all available data
//
// We might arrive here if we read data from the prebuffer but not from the socket.
//只要當前總共讀的數量大於0,就認為完成了,因為無從判斷
done = (totalBytesReadForCurrentRead > 0);
}
// Check to see if we're done,or if we've made progress
//檢查是否讀完
if (done)
{
//完成這次資料的讀取
[self completeCurrentRead];
//如果沒出錯,沒有到邊界,prebuffer中還有可讀資料
if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
{
//讓讀操作離隊,繼續進行下一次讀取
[self maybeDequeueRead];
}
}
//如果這次讀的數量大於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];
}});
}
}
// Check for errors
//檢查錯誤
if (error)
{
//如果有錯直接報錯斷開連線
[self closeWithError:error];
}
//如果是讀到邊界錯誤
else if (socketEOF)
{
[self doReadEOF];
}
//如果是等待
else if (waiting)
{
//如果用的是CFStream,則讀取資料和source無關
//非CFStream形式
if (![self usingCFStreamForTLS])
{
// Monitor the socket for readability (if we're not already doing so)
//重新恢復source
[self resumeReadSource];
}
}
// Do not add any code here without first adding return statements in the error cases above.
}
複製程式碼
特別特別長...接下來就是關閉socket了
5.CLOSE
- (void)closeWithError:(NSError *)error
{
LogTrace();
//先判斷當前queue是不是IsOnSocketQueueOrTargetQueueKey
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),@"Must be dispatched on socketQueue");
//關閉連線超時
[self endConnectTimeout];
if (currentRead != nil) [self endCurrentRead];
if (currentWrite != nil) [self endCurrentWrite];
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
[preBuffer reset];
#if TARGET_OS_IPHONE
{
if (readStream || writeStream)
{
[self removeStreamsFromRunLoop];
if (readStream)
{
CFReadStreamSetClient(readStream,kCFStreamEventNone,NULL,NULL);
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;
}
if (writeStream)
{
CFWriteStreamSetClient(writeStream,NULL);
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;
}
}
}
#endif
[sslPreBuffer reset];
sslErrCode = lastSSLHandshakeError = noErr;
if (sslContext)
{
// Getting a linker error here about the SSLx() functions?
// You need to add the Security Framework to your application.
//關閉sslContext
SSLClose(sslContext);
#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
CFRelease(sslContext);
#else
SSLDisposeContext(sslContext);
#endif
sslContext = NULL;
}
// For some crazy reason (in my opinion),cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run,which in turn releases the source and closes the socket.
//如果這些source都為空,直接只關閉socket就可以
if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource)
{
LogVerbose(@"manually closing close");
if (socket4FD != SOCKET_NULL)
{
LogVerbose(@"close(socket4FD)");
close(socket4FD);
socket4FD = SOCKET_NULL;
}
if (socket6FD != SOCKET_NULL)
{
LogVerbose(@"close(socket6FD)");
close(socket6FD);
socket6FD = SOCKET_NULL;
}
if (socketUN != SOCKET_NULL)
{
LogVerbose(@"close(socketUN)");
close(socketUN);
socketUN = SOCKET_NULL;
//斷開Unix domin socket
unlink(socketUrl.path.fileSystemRepresentation);
socketUrl = nil;
}
}
else
{
//都去取消souce先
if (accept4Source)
{
LogVerbose(@"dispatch_source_cancel(accept4Source)");
dispatch_source_cancel(accept4Source);
// We never suspend accept4Source
accept4Source = NULL;
}
if (accept6Source)
{
LogVerbose(@"dispatch_source_cancel(accept6Source)");
dispatch_source_cancel(accept6Source);
// We never suspend accept6Source
accept6Source = NULL;
}
if (acceptUNSource)
{
LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
dispatch_source_cancel(acceptUNSource);
// We never suspend acceptUNSource
acceptUNSource = NULL;
}
//讀寫source需要resume,否則如果是suspend狀態的話,cancel不會被呼叫
if (readSource)
{
LogVerbose(@"dispatch_source_cancel(readSource)");
dispatch_source_cancel(readSource);
[self resumeReadSource];
readSource = NULL;
}
if (writeSource)
{
LogVerbose(@"dispatch_source_cancel(writeSource)");
dispatch_source_cancel(writeSource);
[self resumeWriteSource];
writeSource = NULL;
}
// The sockets will be closed by the cancel handlers of the corresponding source
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
socketUN = SOCKET_NULL;
}
// If the client has passed the connect/accept method,then the connection has at least begun.
// Notify delegate that it is now ending.
//判斷是否sokcet開啟
BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO;
BOOL isDeallocating = (flags & kDealloc) ? YES : NO;
// Clear stored socket info and all flags (config remains as is)
//清楚socket的相關資訊,和所有標記
socketFDBytesAvailable = 0;
flags = 0;
sslWriteCachedLength = 0;
if (shouldCallDelegate)
{
__strong id theDelegate = delegate;
//判斷是否需要傳自己過去,如果已經被銷燬,就傳nil
__strong id theSelf = isDeallocating ? nil : self;
//呼叫斷開連線的代理
if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
{
dispatch_async(delegateQueue,^{ @autoreleasepool {
[theDelegate socketDidDisconnect:theSelf withError:error];
}});
}
}
}
複製程式碼