CocoaAsyncSocket源码Read(五)
在我们来看flushSSLBuffers
方法之前,我们先来看看这个一直提到的全局缓冲区prebuffer
的定义,它其实就是下面这么一个类的实例:
Part3.GCDAsyncSocketPreBuffer
的定义
@interface GCDAsyncSocketPreBuffer : NSObject
{
//unsigned char
//提前的指针,指向这块提前的缓冲区
uint8_t *preBuffer;
//size_t 它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。
//它可以存储在理论上是可能的任何类型的数组的最大大小
size_t preBufferSize;
//读的指针
uint8_t *readPointer;
//写的指针
uint8_t *writePointer;
}
里面存了3个指针,包括preBuffer起点指针、当前读写所处位置指针、以及一个preBufferSize
,这个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。
//确认读的大小
- (void)ensureCapacityForWrite:(size_t)numBytes
{
//拿到当前可用的空间大小
size_t availableSpace = [self availableSpace];
//如果申请的大小大于可用的大小
if (numBytes > availableSpace)
{
//需要多出来的大小
size_t additionalBytes = numBytes - availableSpace;
//新的总大小
size_t newPreBufferSize = preBufferSize + additionalBytes;
//重新去分配preBuffer
uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
//读的指针偏移量(已读大小)
size_t readPointerOffset = readPointer - preBuffer;
//写的指针偏移量(已写大小)
size_t writePointerOffset = writePointer - preBuffer;
//提前的Buffer重新复制
preBuffer = newPreBuffer;
//大小重新赋值
preBufferSize = newPreBufferSize;
//读写指针重新赋值 + 上偏移量
readPointer = preBuffer + readPointerOffset;
writePointer = preBuffer + writePointerOffset;
}
}
确保prebuffer可用空间的方法:这个方法会重新分配preBuffer
,直到可用大小等于传递进来的numBytes
,已用大小不会变。
//仍然可读的数据,过程是先写后读,只有写的大于读的,才能让你继续去读,不然没数据可读了
- (size_t)availableBytes
{
return writePointer - readPointer;
}
- (uint8_t *)readBuffer
{
return readPointer;
}
- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
{
if (bufferPtr) *bufferPtr = readPointer;
if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
}
//读数据的指针
- (void)didRead:(size_t)bytesRead
{
readPointer += bytesRead;
//如果读了这么多,指针和写的指针还相同的话,说明已经读完,重置指针到最初的位置
if (readPointer == writePointer)
{
// The prebuffer has been drained. Reset pointers.
readPointer = preBuffer;
writePointer = preBuffer;
}
}
//prebuffer的剩余空间 = preBufferSize(总大小) - (写的头指针 - preBuffer一开的指针,即已被写的大小)
- (size_t)availableSpace
{
return preBufferSize - (writePointer - preBuffer);
}
- (uint8_t *)writeBuffer
{
return writePointer;
}
- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
{
if (bufferPtr) *bufferPtr = writePointer;
if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
}
- (void)didWrite:(size_t)bytesWritten
{
writePointer += bytesWritten;
}
- (void)reset
{
readPointer = preBuffer;
writePointer = preBuffer;
}
然后就是对读写指针进行处理的方法,如果读了多少数据readPointer
就后移多少,写也是一样。
而获取当前未读数据,则是用已写指针-已读指针,得到的差值,当已读=已写的时候,说明prebuffer数据读完,则重置读写指针的位置,还是指向初始化位置。
讲完全局缓冲区对于指针的处理,我们接着往下说
Part4.flushSSLBuffers
方法:
//缓冲ssl数据
- (void)flushSSLBuffers
{
LogTrace();
//断言为安全Socket
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
//如果preBuffer有数据可读,直接返回
if ([preBuffer availableBytes] > 0)
{
return;
}
#if TARGET_OS_IPHONE
//如果用的CFStream的TLS,把数据用CFStream的方式搬运到preBuffer中
if ([self usingCFStreamForTLS])
{
//如果flag为kSecureSocketHasBytesAvailable,而且readStream有数据可读
if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
//默认一次读的大小为4KB??
CFIndex defaultBytesToRead = (1024 * 4);
//用来确保有这么大的提前buffer缓冲空间
[preBuffer ensureCapacityForWrite:defaultBytesToRead];
//拿到写的buffer
uint8_t *buffer = [preBuffer writeBuffer];
//从readStream中去读, 一次就读4KB,读到数据后,把数据写到writeBuffer中去 如果读的大小小于readStream中数据流大小,则会不停的触发callback,直到把数据读完为止。
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
//打印结果
LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
//大于0,说明读写成功
if (result > 0)
{
//把写的buffer头指针,移动result个偏移量
[preBuffer didWrite:result];
}
//把kSecureSocketHasBytesAvailable 仍然可读的标记移除
flags &= ~kSecureSocketHasBytesAvailable;
}
return;
}
#endif
//不用CFStream的处理方法
//先设置一个预估可用的大小
__block NSUInteger estimatedBytesAvailable = 0;
//更新预估可用的Block
dispatch_block_t updateEstimatedBytesAvailable = ^{
//预估大小 = 未读的大小 + SSL的可读大小
estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
size_t sslInternalBufSize = 0;
//获取到ssl上下文的大小,从sslContext中
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
//再加上下文的大小
estimatedBytesAvailable += sslInternalBufSize;
};
//调用这个Block
updateEstimatedBytesAvailable();
//如果大于0,说明有数据可读
if (estimatedBytesAvailable > 0)
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
//标志,循环是否结束,SSL的方式是会阻塞的,直到读的数据有estimatedBytesAvailable大小为止,或者出错
BOOL done = NO;
do
{
LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
// Make sure there's enough room in the prebuffer
//确保有足够的空间给prebuffer
[preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
// Read data into prebuffer
//拿到写的buffer
uint8_t *buffer = [preBuffer writeBuffer];
size_t bytesRead = 0;
//用SSLRead函数去读,读到后,把数据写到buffer中,estimatedBytesAvailable为需要读的大小,bytesRead这一次实际读到字节大小,为sslContext上下文
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
//把写指针后移bytesRead大小
if (bytesRead > 0)
{
[preBuffer didWrite:bytesRead];
}
LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
//如果读数据出现错误
if (result != noErr)
{
done = YES;
}
else
{
//在更新一下可读的数据大小
updateEstimatedBytesAvailable();
}
}
//只有done为NO,而且 estimatedBytesAvailable大于0才继续循环
while (!done && estimatedBytesAvailable > 0);
}
}
这个方法有点略长,包含了两种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
(实际也没有这样的方法)。
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,防止反复触发。
Part5.接着我们开始doReadData
正常数据处理流程:
1.
SSL
安全通道; 2.CFStream
类型SSL
; 3.普通数据传输。因为这3种类型的代码,重复部分较大,处理流程基本类似,只不过调用读取方法所有区别
//1.
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
//2.
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
//3.
ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
而SSLRead
回调函数内部,也调用了第3种read
读取,这个我们后面会说。
现在这里我们将跳过前两种(方法部分调用可以见上面的flushSSLBuffers
方法),只讲第3种普通数据的读取操作,而SSL的读取操作,基本一致。
先来看看当前数据包任务是否完成,是如何定义的:
由于框架提供的对外read
接口:
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
将数据读取是否完成的操作,大致分为这3个类型:
1.全读;2读取一定的长度;3读取到某个标记符为止。
当且仅当上面3种类型对应的操作完成,才视作当前包任务完成,才会回调我们在类中声明的读取消息的代理:
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
否则就等待着,直到当前数据包任务完成。
然后我们读取数据的流程大致如下:
先从prebuffer
中去读取,如果读完了,当前数据包任务仍未完成,那么再从socket
中去读取。
而判断包是否读完,都是用我们上面的3种类型,来对应处理的。
作者:Cooci
链接:https://www.jianshu.com/p/5a2df8a6a54e