iOS 移動開發網路 part5.6:CocoaAsyncSocket^write
寫入的部分會比讀取的部程式碼簡單很多.
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
CocoaAsyncSocket
提供的寫入方法只有上面這一個.
按照已經知道的讀取資料的套路也可以大概推出寫入的大概過程.寫入:建GCDAsyncWritePacket
的包加入writeQueue
,呼叫maybeDequeueWrite
.maybeDequeueWrite
進而再呼叫doWriteData
.
doWriteData
與doReadData
一樣,在正式做讀取之前,都會有一些規避判斷(如:flag
表示現在正處於開啟加密的階段,則要呼叫ssl_continueSSLHandshake
),這些在doReadData
內已經說過了,在這就不再贅述了.
doWriteData
的寫入也會被分成三個模組:正常寫入
,CFStream For TLS寫入
,SSL For TLS寫入
.按照前面的套路我們也就知道SSL For TLS寫入
是最複雜的.
三個模組進行之後的善後判斷
也是一樣的,如圖:
1.正常寫入
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;
}
//zc read3:socket寫
ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
LogVerbose(@"wrote to socket = %zd", result);
// Check results
if (result < 0)
{
if (errno == EWOULDBLOCK)
{
waiting = YES;
}
else
{
error = [self errnoErrorWithReason:@"Error in write() function"];
}
}
else
{
bytesWritten = result;
}
單個包寫入完畢的標準是:包所帶的資料量等於
寫入的資料量.
write()後:
result < 0:
(errno == EWOULDBLOCK)==>waiting = YES,需要等待doWriteData的再一次呼叫,進入善後判斷;
其他==>生成報錯,進入善後判斷.
result >= 0:
bytesWritten = result,進入善後判斷.
等待doWriteData的再一次呼叫
也就是writeSource的監聽再一次觸發
,開啟監聽程式碼如下:
//取消可以接受資料的標記,開啟對writeSource的監聽(有空間可寫立刻呼叫doWriteData)
flags &= ~kSocketCanAcceptBytes;
if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
2.CFStream For TLS寫入
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;
}
CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
if (result < 0)
{
error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
}
else
{
bytesWritten = (size_t)result;
// We always set waiting to true in this scenario.
// CFStream may have altered our underlying socket to non-blocking.
// Thus if we attempt to write without a callback, we may end up blocking our queue.
waiting = YES;
}
CFWriteStreamWrite()後:
result < 0:
生成報錯,進入善後判斷.
result >= 0:
bytesWritten = result,waiting = YES,進入善後判斷.
CFStream For TLS寫入
不用writeSource
對bsd socket
進行監聽,所以不用開啟writeSource
的監聽.
if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
可以看到
CFStream
寫入的邏輯特別簡單.這是因為CFStream
內部會自己控制bsd socket
傳輸資料,我們在外圍只呼叫一次CFWriteStreamWrite()
就夠了,所以CFStream
寫入資料非常非常簡單.
3.SSL For TLS寫入
OSStatus result;
BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
BOOL hasNewDataToWrite = YES;
if (hasCachedDataToWrite)
{
size_t processed = 0;
result = SSLWrite(sslContext, NULL, 0, &processed);
if (result == noErr)
{
bytesWritten = sslWriteCachedLength;
sslWriteCachedLength = 0;
if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
{
// We've written all data for the current write.
hasNewDataToWrite = NO;
}
}
else
{
if (result == errSSLWouldBlock)
{
waiting = YES;
}
else
{
error = [self sslError:result];
}
// Can't write any new data since we were unable to write the cached data.
hasNewDataToWrite = NO;
}
}
if (hasNewDataToWrite)
{
const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+ currentWrite->bytesDone
+ bytesWritten;
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
{
bytesToWrite = SIZE_MAX;
}
size_t bytesRemaining = bytesToWrite;
BOOL keepLooping = YES;
while (keepLooping)
{
const size_t sslMaxBytesToWrite = 32768;
size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
size_t sslBytesWritten = 0;
result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
if (result == noErr)
{
buffer += sslBytesWritten;
bytesWritten += sslBytesWritten;
bytesRemaining -= sslBytesWritten;
keepLooping = (bytesRemaining > 0);
}
else
{
if (result == errSSLWouldBlock)
{
waiting = YES;
sslWriteCachedLength = sslBytesToWrite;//沒寫給對面,寫入了sslContext內,需要記錄sslWriteCachedLength
}
else
{
error = [self sslError:result];
}
keepLooping = NO;
}
} // while (keepLooping)
} // if (hasNewDataToWrite)
SSL For TLS寫入
的程式碼很清晰的分成了兩塊:寫入快取的資料,寫入包內的資料(寫入都有快取,厲害了吧!).我們先說寫入包內的資料
,再說寫入快取的資料
.
- 寫入包內的資料
單次呼叫SSLWrite()
寫入資料的大小是MIN(bytesRemaining, sslMaxBytesToWrite)
(包內所剩資料量
與ssl能寫的最大資料量
小的那一個).
SSLWrite()後:
沒報錯:就迴圈呼叫SSLWrite()做寫入,直到寫完;
擁塞報錯:把sslBytesToWrite的計給sslWriteCachedLength,停止迴圈,進入善後判斷,需要等待doWriteData的再一次呼叫;(這就是ssl快取的由來)
其他報錯:停止迴圈,生成報錯,進入善後判斷.
正常寫入
與SSL For TLS寫入
用的都是writeSource
對bsd socket
的監聽(上篇文章反覆說,這就不贅述了).
- 寫入快取的資料
SSLWrite()後:
沒報錯:寫入的資料量等於sslWriteCachedLength,再進行寫入包內的資料;
擁塞報錯:不再寫入包內的資料,waiting = YES,需要等待doWriteData的再一次呼叫,進入善後判斷;
其他報錯:不再寫入包內的資料,生成報錯,進入善後判斷;
SSLWrite()
方法流程如下:
SSLWrite()
SSLWriteFunction()
- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
{
if (!(flags & kSocketCanAcceptBytes))
{
// Unable to write.
//
// Need to wait for writeSource to fire and notify us of
// available space in the socket's internal write buffer.
[self resumeWriteSource];
*bufferLength = 0;
return errSSLWouldBlock;
}
size_t bytesToWrite = *bufferLength;
size_t bytesWritten = 0;
BOOL done = NO;
BOOL socketError = NO;
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
ssize_t result = write(socketFD, buffer, bytesToWrite);
if (result < 0)
{
if (errno != EWOULDBLOCK)
{
socketError = YES;
}
flags &= ~kSocketCanAcceptBytes;
}
else if (result == 0)
{
flags &= ~kSocketCanAcceptBytes;
}
else
{
bytesWritten = result;
done = (bytesWritten == bytesToWrite);
}
*bufferLength = bytesWritten;
if (done)
return noErr;
if (socketError)
return errSSLClosedAbort;
return errSSLWouldBlock;
}
SSL For TLS寫入
最終轉換成bsd socket
的寫入,程式碼也是一目瞭然,就不多做解釋了.
相關文章
- 移動網際網路伺服器端開發伺服器
- iOS開發之網路篇iOS
- 移動開發—iOS日常面試問題移動開發iOS面試
- 移動網站開發——CSS網站CSS
- 全網唯一:移動網際網路伺服器端開發!伺服器
- PHP移動網際網路開發(1)——環境搭建及配置PHP
- 第三屆中國移動網際網路測試開發大會
- 移動網際網路
- [iOS] Socket & CocoaAsyncSocket介紹與使用iOS
- 移動的微博,移動的網際網路薦
- 移動網際網路業務創新發展報告
- 移動網際網路下半場小程式開啟產業網際網路時代產業
- 移動網際網路的風口已開始衰竭
- 【移動端開發】移動端開發基礎問題
- 移動網際網路使用者行為研究-移動網際網路改變生活
- 李開復:移動網際網路五大趨勢
- 人民網:2016年中國移動網際網路發展
- 移動網際網路仍需“有形之手”
- 美國移動網際網路模式分析模式
- 網際網路移動在“雲”端
- 移動網際網路的前世今生
- GOOGLE:移動網際網路案例分析Go
- 移動網際網路的支付作用
- 移動網際網路的快速發展成就了哪些行業?行業
- 口袋中的商機–移動網際網路發展狀況
- 移動遊戲開發商為何更中意iOS平臺?遊戲開發iOS
- iOS開發:網頁JS與OC互動(JavaScriptCore)iOS網頁JSJavaScript
- YonBuilder移動開發-移動原生外掛開發環境配置教程UI移動開發開發環境
- iOS開發基礎109-網路安全iOS
- comScore:移動APP佔據移動網際網路主導地位APP
- 移動開發即服務,騰訊雲移動開發平臺打造開發新模式移動開發模式
- 手機一代 撬動網際網路版圖 95後影響移動網際網路發展
- 移動端開發模式模式
- 移動端開發技巧
- 移動開發規範移動開發
- VRI:日本移動網際網路網站排名VR網站
- 移動網際網路正在終結傳統網際網路
- 移動網際網路時代旅遊消費者的決策路徑發現