注册

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,这个sizepreBuffer所指向的位置,在内存中分配的空间大小。

我们来看看它的几个方法:

//初始化
- (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的数据处理:

  1. CFStream类型:我们会调用下面这个函数去从stream并且读取数据并解密:
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);

数据被读取到后,直接转移到了prebuffer中,并且调用:

[preBuffer didWrite:result];

让写指针后移读取到的数据大小。
这里有两个关于CFReadStreamRead方法,需要注意的问题:
1)就是我们调用它去读取4KB数据,并不仅仅是只读这么多,而是因为这个方法是会递归调用的,它每次只读4KB,直到把stream中的数据读完。
2)我们之前设置的CFStream函数的回调,在数据来了之后只会被触发一次,以后数据再来都不会触发。直到我们调用这个方法,把stream中的数据读完,下次再来数据才会触发函数回调。这也是我们在使用CFStream的时候,不需要担心像source那样,有数据会不断的被触发回调,而需要挂起像source那样挂起stream(实际也没有这样的方法)。

  1. SSL安全通道类型:这里我们主要是循环去调用下面这个函数去读取数据:
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);

其他的基本和CFStream一致

这里需要注意的是SSLRead这个方法,并不是直接从我们的socket中获取到的数据,而是从我们一开始绑定的SSL回调函数中,得到数据。而回调函数本身,也需要调用read函数从socket中获取到加密的数据。然后再经由SSLRead这个方法,数据被解密,并且传递给buffer

至于SSLRead绑定的回调函数,是怎么处理数据读取的,因为它处理数据的流程,和我们doReadData后续数据读取处理基本相似,所以现在暂时不提。

我们绕了一圈,讲完了这个包为空或者当前暂停状态下的前置处理,总结一下:
  1. 就是如果是SSL类型的数据,那么先解密了,缓冲到prebuffer中去。
  2. 判断当前socket可读数据大于0,非CFStreamSSL类型,则挂起source,防止反复触发。
Part5.接着我们开始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读取到某个标记符为止。

当且仅当上面3种类型对应的操作完成,才视作当前包任务完成,才会回调我们在类中声明的读取消息的代理:

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

否则就等待着,直到当前数据包任务完成。

然后我们读取数据的流程大致如下:

先从prebuffer中去读取,如果读完了,当前数据包任务仍未完成,那么再从socket中去读取。
而判断包是否读完,都是用我们上面的3种类型,来对应处理的。



作者:Cooci
链接:https://www.jianshu.com/p/5a2df8a6a54e






0 个评论

要回复文章请先登录注册