- 源码拆解阅读
读取数据的工作因为牵扯到断包和粘包,所以会特别复杂.再加上TLS又涉及数据的解密,所以会更复杂.文章开篇就说过了- (void)doReadData
有747行代码,它之所以那么长就是因为将正常读取
、cf for TLS读取
、ssl for TLS读取
混在了一个方法里.那为什么不进行分流判断将1个大方法拆成3个小方法呢?
因为正常读取
与ssl for TLS读取
有很多相同的操作逻辑(如:开启与停止dispatch_source readSource
的监听),cf for TLS读取
与ssl for TLS读取
也有很多相同的操作逻辑(如:数据提前解密).
从源码作者的角度说:1个方法拆成3个方法会有很多冗余代码!
从源码读者的角度说:想更好的读懂源码就得把1个方法拆成3个方法.然后再合并理解三个流程.
- 断包和粘包
socket
夹在应用层与传输层(我们以TCP为例来讲解)之间.应用层将3条消息发给socket
,socket
将消息交给TCP,TCP会按照网络的MTU(网络最大传输单元)来决定3条消息分成几个TCP报文段(3条信息被1个TCP报文段封装发出==>粘包;3条消息有1条特别大被分成5个TCP报文段才发出==>断包).这样3条有独立含义
的消息被硬分成了几个TCP报文段进行传输.接收方在收到报文段后则需要解读出
有独立含义
的消息.这样就需要通信双方约定读取边界以便于我们对数据进行截取与解读
.
通行做法是:
约定读取边界读取消息头
;
消息头
内标明消息体的长度
;
按读到的消息体的长度
再读取消息体
.
Demo:即时通讯的数据粘包、断包处理实例------ by 涂耀辉
- 读取方式
CocoaAsyncSocket
无论是读取、写入、还是开启TLS
都是由建包加入队列开始的,读对应的是GCDAsyncReadPacket *currentRead
.
CocoaAsyncSocket
提供了9个方法来建立GCDAsyncReadPacket
来做读取数据的工作.
以上方法可以分为:
全读(这种除了简单的实验测试,并无任何实际效用)
读边界(传入term)
读定长(传入length)
读边界 与 我们上面讲得 读取消息头 对应;
读定长 与 我们上面讲得 读取消息体 对应;
1. 正常读取
- (void)zc_doReadData_Nomal
{
LogTrace();
if ((currentRead == nil) || (flags & kReadsPaused))
{
LogVerbose(@"No currentRead or kReadsPaused");
{
if (socketFDBytesAvailable > 0)
{
//zc read:防止监听重复开火
[self suspendReadSource];
}
}
return;
}
BOOL hasBytesAvailable = NO;
unsigned long estimatedBytesAvailable = 0;
{
estimatedBytesAvailable = socketFDBytesAvailable;
hasBytesAvailable = (estimatedBytesAvailable > 0);
}
if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
{
LogVerbose(@"No data available to read...");
NSLog(@"zc func call -- socket与preBuffer都没有可读数据-----------packet stay");
[self resumeReadSource];
return;
}
BOOL done = NO; // Completed read operation
NSError *error = nil; // Error occurred
NSUInteger totalBytesReadForCurrentRead = 0;
//
// STEP 1 - READ FROM PREBUFFER
//
if ([preBuffer availableBytes] > 0)
{
NSUInteger bytesToCopy;
if (currentRead->term != nil)
{
bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];//zc read:读取到一个明确的界限
}
else
{
bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
}
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
currentRead->bytesDone;
memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
NSLog(@"zc func call -- READ FROM PREBUFFER -->currentRead从preBuffer读入%zd个字节",bytesToCopy);
[preBuffer didRead:bytesToCopy];
LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
currentRead->bytesDone += bytesToCopy;
totalBytesReadForCurrentRead += bytesToCopy;
if (currentRead->readLength > 0)
{
done = (currentRead->bytesDone == currentRead->readLength);
}
else if (currentRead->term != nil)
{
if (!done && currentRead->maxLength > 0)
{
if (currentRead->bytesDone >= currentRead->maxLength)
{
error = [self readMaxedOutError];
}
}
}
else
{
done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
}
}
//
// STEP 2 - READ FROM SOCKET
//
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");
BOOL readIntoPreBuffer = NO;
uint8_t *buffer = NULL;
size_t bytesRead = 0;
{
NSUInteger bytesToRead;
if (currentRead->term != nil)//zc read:外围不提供缓存块,而且是读边界,缓存长度为0;所以肯定要写入socket->preBuffer
{
bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
shouldPreBuffer:&readIntoPreBuffer];
}
else
{
bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
}
if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
bytesToRead = SIZE_MAX;
}
if (readIntoPreBuffer)
{
[preBuffer ensureCapacityForWrite:bytesToRead];
buffer = [preBuffer writeBuffer];
}
else
{
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ currentRead->startOffset
+ currentRead->bytesDone;
}
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
LogVerbose(@"read from socket = %i", (int)result);
if (readIntoPreBuffer) {
NSLog(@"zc func call -- READ FROM SOCKET -->preBuffer从socket读入%zd个字节",result);
}else{
NSLog(@"zc func call -- READ FROM SOCKET -->currentRead从socket读入%zd个字节",result);
}
if (result < 0)//zc read:读取报错 或者 是陷入阻塞
{
if (errno == EWOULDBLOCK)
waiting = YES;
else
error = [self errnoErrorWithReason:@"Error in read() function"];
socketFDBytesAvailable = 0;
}
else if (result == 0)//zc read:读取结束符
{
socketEOF = YES;
socketFDBytesAvailable = 0;
}
else //zc read:读取到正常数据
{
bytesRead = result;
if (bytesRead < bytesToRead)//zc read:bytesRead真正读取到的 bytesToRead预备读取到的
{
socketFDBytesAvailable = 0;
}
else
{
if (socketFDBytesAvailable <= bytesRead)
socketFDBytesAvailable = 0;
else
socketFDBytesAvailable -= bytesRead;
}
if (socketFDBytesAvailable == 0)//zc read:source监听到来的数据,被全部读完
{
waiting = YES;
}
}
}
if (bytesRead > 0)
{
if (currentRead->readLength > 0)
{
NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = (currentRead->bytesDone == currentRead->readLength);
}
else if (currentRead->term != nil)
{
if (readIntoPreBuffer)
{
[preBuffer didWrite:bytesRead];
LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy);
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
memcpy(readBuf, [preBuffer readBuffer], bytesToCopy);//zc read:sockect收到44个字节的数据,preBuffer从socket读入44个字节,currentRead从preBuffer读入38个字节
NSLog(@"zc func call -- READ FROM SOCKET -->currentRead从preBuffer读入%zd个字节",bytesToCopy);
[preBuffer didRead:bytesToCopy];
LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
currentRead->bytesDone += bytesToCopy;
totalBytesReadForCurrentRead += bytesToCopy;
}
else
{
NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
if (overflow == 0)
{
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = YES;
}
else if (overflow > 0)
{
NSInteger underflow = bytesRead - overflow;
LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow);
[preBuffer ensureCapacityForWrite:overflow];
uint8_t *overflowBuffer = buffer + underflow;
memcpy([preBuffer writeBuffer], overflowBuffer, overflow);
[preBuffer didWrite:overflow];
LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
currentRead->bytesDone += underflow;
totalBytesReadForCurrentRead += underflow;
done = YES;
}
else
{
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = NO;
}
}
if (!done && currentRead->maxLength > 0)
{
if (currentRead->bytesDone >= currentRead->maxLength)
{
error = [self readMaxedOutError];
}
}
}
else
{
if (readIntoPreBuffer)
{
[preBuffer didWrite:bytesRead];
[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
memcpy(readBuf, [preBuffer readBuffer], bytesRead);
[preBuffer didRead:bytesRead];
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
else
{
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
done = YES;
}
} // if (bytesRead > 0)
} // if (!done && !error && !socketEOF && hasBytesAvailable)
if (!done && currentRead->readLength == 0 && currentRead->term == nil)
{
done = (totalBytesReadForCurrentRead > 0);
}
NSLog(@"zc func call 数据未全部读取完毕,等待source_handler trigger;done->%zd,waiting->%zd",done,waiting);
if (done)
{
[self completeCurrentRead];
if (!error && (!socketEOF || [preBuffer availableBytes] > 0))//zc read:读取没有报错 且 (没读到结尾 或者 socket->preBuffer还有数据) 都要继续读取
{
[self maybeDequeueRead];
}
}
else if (totalBytesReadForCurrentRead > 0)
{
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
{
long theReadTag = currentRead->tag;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
}});
}
}
if (error)
{
[self closeWithError:error];
}
else if (socketEOF)
{
[self doReadEOF];
}
else if (waiting)
{
[self resumeReadSource];
}
}
以上方法是我剔除了TLS
读取功能的正常读取数据的代码.
在读取数据的时候所有数据的最后都会流向GCDAsyncReadPacket * currentRead->buffer
.而在这之前,数据除了会出现在bsd socket
内,还有可能出现在GCDAsyncSocket * socket->preBuffer
内.
数据在bsd socket
内,直接读给currentRead->buffer
不就一了百了
了吗?要GCDAsyncSocket * socket->preBuffer
干嘛?
在读边界的时候,我们必须将bsd socket
内的数据先读到一个地方
,然后在检测数据内有没有包含边界,这个地方
就是GCDAsyncSocket * socket->preBuffer
.除了作为读边界的预备阵地
,GCDAsyncSocket * socket->preBuffer
也会暂时存储着未被应用层
应用的数据(如:GCDAsyncSocket * socket->preBuffer
从bsd socket
读出6k的数据,包读到边界只拿走了3k,剩下的3k还得放在GCDAsyncSocket * socket->preBuffer
内,等待下次的读取).
所以总结来说正常读取数据就3个走向:
[bsd socket] move data into currentRead->buffer
[bsd socket] move data into socket->preBuffer
socket->preBuffer move data into currentRead->buffer
借助演绎代码即时通讯的数据粘包、断包处理实例------ by 涂耀辉,我们会介绍:
读边界^读消息头
读定长^读消息体
全读
没有在演绎代码内出现,也比较简单,我会提一下.
1.1 触发doReadData
1.对正常数据读取依靠的dispatch_source readSource
对bsd socket
的监听,一旦有数据来就会触发doReadData
.
2.创建currentRead
也会触发doReadData
.
1.随机触发:readSource的触发会很随机,readSource的触发调用doReadData时会出现没有currentRead的情况,此时会暂停对readSource的监听,并终止方法的调用.后面会恢复对readSource的监听.
2.读取的线程安全:readSource的触发会调用doReadData, currentRead的构建完成也会调用doReadData,不过调用doReadData全走socketQueue,socketQueue是串行队列,所有是线程安全的.
1.2 doReadData进行时
1.2.1 读边界^读消息头
在建立连接后接受方创建读边界的包GCDAsyncReadPacket * currentRead
.代码如下:
[newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:110];
[GCDAsyncSocket CRLFData]
就是边界.
bsd socket
有数据来了会触发dispatch_source readSource
的回调,调用- (void)doReadData
.
step A:读preBuffer
当然起始的时候socket->preBuffer
是没有数据的,所以直接进入step B
;
读取数据是一个循环往复的过程,所以socket->preBuffer
在读取中途肯定是有数据的.
socket->preBuffer
有数据,则检测socket->preBuffer
的数据有没有规定好的边界?
有,socket->preBuffer
就将到边界为止的数据交给currentRead->buffer
,然后不进入step B
,直接将包获取到的所有数据通过代理方法通知外围代理,完毕;
没有,socket->preBuffer
则将所有数据都交给currentRead->buffer
,进入step B
.
step B:读bsd socket
将bsd socket
的所有数据都搬运到socket->preBuffer
上.
检测socket->preBuffer
的数据有没有规定好的边界?
有,socket->preBuffer
就将到边界为止的数据交给currentRead->buffer
,然后将包获取到的所有数据通过代理方法通知外围代理,完毕;
没有,socket->preBuffer
则将所有数据都数据交给currentRead->buffer
,等待bsd socket
再一次有数据到来,直到读取到边界再通知代理.
关于读边界的补充:
我们用[newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:110];
生成的currentRead->buffer的长度为0,这样currentRead->buffer肯定是不够写入[bsd socket]的所有数据的.
所以数据先到socket->preBuffer,然后socket->preBuffer再交给currentRead->buffer.
但我们前面也已经说过有9个方法可以生成currentRead,有的方法生成的currentRead就可以同时拥有边界与缓冲块.
一旦currentRead->buffer够写入[bsd socket]的所有数据,就会有另外一条数据搬运逻辑.
读边界数据搬运逻辑1(前面说过的):
currentRead->buffer不够写入[bsd socket]的所有数据,则所有数据先放入socket->preBuffer;
检测socket->preBuffer的数据有没有规定好的边界?
有,socket->preBuffer就将到边界为止的数据交给currentRead->buffer,然后将包获取到的所有数据通过代理方法通知外围代理,完毕;
没有,socket->preBuffer则将所有数据都数据交给currentRead->buffer,等待bsd socket再一次有数据到来,直到读取到边界再通知代理.
读边界数据搬运逻辑2:
currentRead->buffer够写入[bsd socket]的所有数据,则所有数据先放入currentRead->buffer;
检测currentRead->buffer的数据有没有规定好的边界?
有,currentRead->buffer就将超出边界的数据退给socket->preBuffer,然后将包获取到的所有数据通过代理方法通知外围代理,完毕;
没有,则等待bsd socket再一次有数据到来,直到读取到边界再通知代理.
代理获取到数据,转换成消息头,消息头内有消息体的长度.往下看......
1.2.2 读定长^读消息体
代理知道了消息体的长度后,创建读定长的包GCDAsyncReadPacket * currentRead
.代码如下:
[sock readDataToLength:packetLength withTimeout:-1 tag:110];
调用- (void)doReadData
step A:读preBuffer
如果socket->preBuffer
有数据,能把GCDAsyncReadPacket * currentRead
读满,则通知代理,完毕;未读满则进入step B
;
如果socket->preBuffer
没有数据,直接进入step B
;
step B:读bsd socket
bsd socket
的数据多余GCDAsyncReadPacket * currentRead
需要的数据量==>包读满,通知代理,完毕;
bsd socket
的数据少余GCDAsyncReadPacket * currentRead
需要的数据量==>所有数据都给包,包读未满,等待bsd socket
再一次有数据到来,直到读满定长再通知代理;
bsd socket
的数据等余GCDAsyncReadPacket * currentRead
需要的数据量==>包读满,通知代理,完毕;
1.2.3 全读
把socket->preBuffer
与bsd socket
内的所有数据都读到包内,通知代理,完毕;
2. cf for TLS数据读取
在看懂为正常读取
的简化读取方法- (void)zc_doReadData_Nomal
后,我们就可以回归真正的- (void)doReadData
,将读取与解密混合起来一起看.
接下来,我们将分3块:触发doReadData
,提前解密
,doReadData进行时
来说说cf for TLS的数据读取
.
2.1 触发doReadData
前面已经提过cf for TLS
触发doReadData
不再依靠dispatch_source_t readSource
对bsd socket
的监听.
在- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
方法内会向readStream
注册回调.
CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)
static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
switch(type)
{
case kCFStreamEventHasBytesAvailable:
{
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
if (asyncSocket->readStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
// (A callback related to the tcp stream, but not to the SSL layer).
if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
{
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
[asyncSocket cf_finishSSLHandshake];
}
}
else
{
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
[asyncSocket doReadData];
}
}});
break;
}
default:
{
NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
if (error == nil && type == kCFStreamEventEndEncountered)
{
error = [asyncSocket connectionClosedError];
}
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - Other");
if (asyncSocket->readStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
[asyncSocket cf_abortSSLHandshake:error];
}
else
{
[asyncSocket closeWithError:error];
}
}});
break;
}
}
}
CFReadStreamCallback
内就调用了doReadData
.
当然创建currentRead
也会触发doReadData
.
2.2 提前解密
有数据来调用doReadData
会出现没有currentRead
的情况,这时如果是正常数据读取
,则会停止doReadData
的调用.而如果是加密数据读取则会有下文:
加密数据读取涉及到解密,currentRead要装载的是解密的数据,所以在没有currentRead的情况下,我可以提前对数据进行解密;
那提前解密的数据放在哪呢?preBuffer内.
cf for TLS
提前解密与读取中解密所用的代码是一样,所以解密代码只在这做说明.
cf for TLS
和ssl for TLS
提前解密用的都是flushSSLBuffers
.(以下代码删除了ssl for TLS
提前解密的代码)
- (void)flushSSLBuffers
{
LogTrace();
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
if ([preBuffer availableBytes] > 0)
{
// Only flush the ssl buffers if the prebuffer is empty.
// This is to avoid growing the prebuffer inifinitely large.
return;
}
#if TARGET_OS_IPHONE
if ([self usingCFStreamForTLS])
{
if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
CFIndex defaultBytesToRead = (1024 * 4);
[preBuffer ensureCapacityForWrite:defaultBytesToRead];
uint8_t *buffer = [preBuffer writeBuffer];
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);//zc read:CFStream Read
LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
if (result > 0)
{
[preBuffer didWrite:result];
}
flags &= ~kSecureSocketHasBytesAvailable;
}
return;
}
#endif
}
代码很简单,preBuffer
向CFStream
读1024 * 4
,具体读到多少看返回结果.
2.3 doReadData进行时
上面讲到的提前解密会发生在没有currentRead
的情况下,而一旦有了currentRead
,doReadData
就会被走通.
读定长,读边界,全读的方式在正常数据读取的时候已经全部讲过.特别说明以下情况:
读边界的包
自带缓存块的情况,有些复杂,而且正常数据读取
与加密数据读取
对于这种情况的处理逻辑上很相似,以下就不再说明了.
全读的包
以下也不再说明了.
2.3.1 读边界
step A:读preBuffer
因为preBuffer
内的数据已经是解密的,所以这个过程和正常读取
读边界的流程是一样.简而言之:preBuffer
包含边界则将到边界为止的数据
全交给包,通知代理,完毕;preBuffer
不包含边界,将所有数据全交给包,进入step B
.
step B:读bsd socket
由于bsd socket
内的数据是加密的,我们只能读一个预估的大小(如:1024 * 32).加密的数据解密后读到preBuffer
内;(解密过程的代码前面的提前解密部分
已经说过,这里不再说)
preBuffer
包含边界则将到边界为止的数据
全交给包,通知代理,完毕;preBuffer
不包含边界,将所有数据全交给包,等待doReadData
的下一次触发.
2.3.2 读定长
step A:读preBuffer
如果socket->preBuffer
有数据,能把GCDAsyncReadPacket * currentRead
读满,则通知代理,完毕;未读满则进入step B
;
step B:读bsd socket
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSUInteger result;
if (readLength > 0)
{
// Read a specific length of data
result = MIN(defaultValue, (readLength - bytesDone));
// There is no need to prebuffer since we know exactly how much data we need to read.
// Even if the buffer isn't currently big enough to fit this amount of data,
// it would have to be resized eventually anyway.
if (shouldPreBufferPtr)
*shouldPreBufferPtr = NO;
}
else
{
......
}
return result;
}
有定长,数据不会读入preBuffer
.
由于bsd socket
内的数据是加密的,我们得先预估一个大小(如:1024 * 32),然后用预估大小
与定长
做比对来确认读多少数据.
具体能读出多少数据得看得看CFReadStreamRead()的返回值
.
解密后的数据读到currentRead->buffer
内,检测是否读满定长,读满,通知代理,完毕;未读满,等待doReadData
的下一次触发.
3. ssl for TLS数据读取
3.1 触发doReadData
1.依靠的dispatch_source readSource
对bsd socket
的监听,一旦有数据来就会触发doReadData
.
2.创建currentRead
也会触发doReadData
.
3.2 提前解密
- 新加两道缓存
// The first buffer is one we create.
// SecureTransport often requests small amounts of data.
// This has to do with the encypted packets that are coming across the TCP stream.
// But it's non-optimal to do a bunch of small reads from the BSD socket.
// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
// and may store excess in the sslPreBuffer.
// The second buffer is within SecureTransport.
// As mentioned earlier, there are encrypted packets coming across the TCP stream.
// SecureTransport needs the entire packet to decrypt it.
// But if the entire packet produces X bytes of decrypted data,
// and we only asked SecureTransport for X/2 bytes of data,
// it must store the extra X/2 bytes of decrypted data for the next read.
sslPreBuffer//将加密的数据从bsd socket内读出,预存;(存储着加密数据)
sslInternalBuf//解密的数据却未被读出,暂存;(存储着解密数据)
那么为什么需要这样两道缓存呢?
sslPreBuffer
:我们外围传入缓存块,传入的缓存块只用于写入解密的数据,至于加密的数据
能解密出多少解密的数据
我们是不知道的.这就需要有缓存块来缓存从bsd socket
内读出加密的数据
;
sslInternalBuf
:加密的数据包到终端后我们需要整个包一起解密(比如:解密出数据1200字节),但外围读取有可能只需要读取部分(比如:400字节),那另外的800字节放在哪呢?这就必须放在sslInternalBuf
内;
CFStream for TLS
的读取过程中好像没见到这两道缓存?
ssl for TLS
的情况下,sslInternalBuf
是SecureTransport
内部管理的,我要管的只有sslPreBuffer
;
而对于CFStream
来说这两道缓存它内部都做好了管理,我们都不用管.
ps:由于CFStream
内部封装得很好,从缓存管理到读取方式,CFStream for TLS
都会显得简单很多.
- 循环读取
ssl for TLS
提前解密与读取中解密所用的代码是一样,所以解密代码只在这做说明,原理也只在这介绍.
读取方法如下:
- (void)flushSSLBuffers
{
LogTrace();
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
if ([preBuffer availableBytes] > 0)
{
// Only flush the ssl buffers if the prebuffer is empty.
// This is to avoid growing the prebuffer inifinitely large.
return;
}
__block NSUInteger estimatedBytesAvailable = 0;
dispatch_block_t updateEstimatedBytesAvailable = ^{
// Figure out if there is any data available to be read
//
// socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket ==>加密的
// [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket ==>加密的
// sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered ==>解密的
//
// We call the variable "estimated" because we don't know how many decrypted bytes we'll get
// from the encrypted bytes in the sslPreBuffer.
// However, we do know this is an upper bound on the estimation.
estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
estimatedBytesAvailable += sslInternalBufSize;
};
updateEstimatedBytesAvailable();
if (estimatedBytesAvailable > 0)
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
BOOL done = NO;
do
{
LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
// Make sure there's enough room in the prebuffer
[preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
// Read data into prebuffer
uint8_t *buffer = [preBuffer writeBuffer];
size_t bytesRead = 0;
//zc read:sslRead1(数据被解密)
/*
调用SSLRead,内部会自动注册好的SSLReadFunction,读出加密数据
SSLRead内部会加密数据解密然后传出
*/
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
if (bytesRead > 0)
{
[preBuffer didWrite:bytesRead];
}
LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
if (result != noErr)
{
done = YES;
}
else
{
updateEstimatedBytesAvailable();
}
} while (!done && estimatedBytesAvailable > 0);
}
}
看上去就比cf for TLS
提前解密复杂得多,对吧!
首先我们来看看ssl for TLS
,读取方法的链条.
我们在外围调用SSLRead()
;
SSLReadFunction()
方法是绑定到sslContext
上的,我们调用SSLRead()
,就会触发SSLReadFunction()
的调用;
SSLReadFunction()
内部调用- (OSStatus)sslReadWithBuffer:(void *)out_pass_buffer length:(size_t *)bufferLength
;
ssl for TLS
与cf for TLS
解密读取方式对比如下:
cf for TLS解密读取
CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
//方法到此为止,我们不知道任何细节;
ssl for TLS解密读取
SSLRead()
//该方法会被循环调用;
//方法由系统实现,除了调用SSLReadFunction()外还做了解密数据的工作;
- (OSStatus)sslReadWithBuffer:(void *)out_pass_buffer length:(size_t *)bufferLength;
//该方法也会被循环调用;
//CocoaAsyncSocket源码实现该方法;
//sslPreBuffer就是在这个方法里做读写操作的;
//该方法在真实调用过程,也分读bsd socket和读sslPreBuffer,简而言之:该方法等同一个小型的doReadData;
cf for TLS
的解密读取是完全封闭的,而ssl for TLS
解密读取是半封闭的.
3.3 doReadData进行时
cf for TLS
的doReadData进行时的动作
与ssl for TLS
的doReadData进行时的动作
几乎是一样.(除了解密数据的代码,ssl for TLS
解密数据的代码提前解密部分
已经交代过,这里不再多说)
3.3.1 读边界
step A:读preBuffer
因为preBuffer
内的数据已经是解密的,所以这个过程和正常读取
读边界的流程是一样.简而言之:preBuffer
包含边界则将到边界为止的数据
全交给包,通知代理,完毕;preBuffer
不包含边界,将所有数据全交给包,进入step B
.
step B:读bsd socket
由于bsd socket
内的数据是加密的,我们只能读一个预估的大小(如:1024 * 32).加密的数据解密后读到preBuffer
内;
preBuffer
包含边界则将到边界为止的数据
全交给包,通知代理,完毕;preBuffer
不包含边界,将所有数据全交给包,等待doReadData
的下一次触发.
3.3.2 读定长
step A:读preBuffer
如果socket->preBuffer
有数据,能把GCDAsyncReadPacket * currentRead
读满,则通知代理,完毕;未读满则进入step B
;
step B:读bsd socket
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSUInteger result;
if (readLength > 0)
{
// Read a specific length of data
result = MIN(defaultValue, (readLength - bytesDone));
// There is no need to prebuffer since we know exactly how much data we need to read.
// Even if the buffer isn't currently big enough to fit this amount of data,
// it would have to be resized eventually anyway.
if (shouldPreBufferPtr)
*shouldPreBufferPtr = NO;
}
else
{
......
}
return result;
}
有定长,数据不会读入preBuffer
.
由于bsd socket
内的数据是加密的,我们得先预估一个大小(如:1024 * 32),然后用预估大小
与定长
做比对来确认读多少数据.
具体能读出多少数据得看循环读取
能读出了多少数据.
解密后的数据读到currentRead->buffer
内,检测是否读满定长,读满,通知代理,完毕;未读满,等待doReadData
的下一次触发.