因为KTVHttpCache的底层主要基于GCDAsyncSocket实现,所以先对GCDAsyncSocket的实现做一个简单分析。
GCDAsyncSocket可以看做是对操作系统的socket接口的一个封装,并利用OC的GCD做异步。利用socket建立连接过程可以参看下图:
图片来自《Unix网络编程》图4.1GCDAsyncSocket的主要成员
首先罗列一下GCDAsyncSocket的主要成员对象(为了简洁起见把跟分析无关的略去了),现在看不懂没关系,后面的分析会涉及到。
@interface GCDAsyncSocket ()
{
__weak id delegate; //代理对象,由上层设置
dispatch_queue_t delegateQueue; //由上层设置的代理串行Queue
int socket4FD; //socket描述符,ipv4和ipv6只用其中一个
int socket6FD;
dispatch_queue_t socketQueue; //处理socket的串行Queue
NSMutableArray *readQueue; //读队列,用于读取客户端发送过来的数据
NSMutableArray *writeQueue; //写队列,用于发送数据给客户端
GCDAsyncReadPacket *currentRead; //读packet
GCDAsyncWritePacket *currentWrite; //写packet
GCDAsyncSocketPreBuffer *preBuffer; //辅助读写的
}
1.GCDAsyncSocket初始化
代码简略如下:
- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
}
return self;
}
代码很简单没什么要解释的,唯一要注意的是,在GCDAsyncSocket是通过代理对象delegate来返回数据给上层的,比如后面要介绍的KTVHTTPServer,它在创建一个GCDAsyncSocket对象时,把它自身做为delegate传给了GCDAsyncSocket。
2.acceptOnInterface接口
acceptOnInterface接口主要是封装了socket建立listen套接字,并调用accept等到客户端连接的逻辑。
3.doAccept接口
在上面的acceptOnInterface接口的最后,创建了listen套接字后,随后就是调用doAccept函数,doAccept函数主要就是调用accept接口后挂起,等待客户端的连接的到来,当有客户端连接到来时,再分配一个新的socket套接字来接收客户端的数据。
4.setupReadAndWriteSourcesForNewlyConnectedSocket接口
由上一步可知,当accpet返回一个新的socket时,就为该socket创建一个新的GCDAsyncSocket对象,该对象就用来处理服务器与对应连接的客户端的数据传输,为了进行数据传输,需要调用setupReadAndWriteSourcesForNewlyConnectedSocket接口来创建读写事件源。代码也挺简单,就直接贴出来不分析了(为减少篇幅,代码进行了删减)。
- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
{
readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
socketFDBytesAvailable = dispatch_source_get_data(readSource);
if (socketFDBytesAvailable > 0)
[self doReadData];
else
[self doReadEOF];
}});
dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
flags |= kSocketCanAcceptBytes;
[self doWriteData];
}});
//...
dispatch_source_set_cancel_handler(readSource, ^{
//...
});
dispatch_source_set_cancel_handler(writeSource, ^{
//...
});
//...
}
5.doReadData接口
doReadData接口用于处理读取从客户端发送过来的数据。首先看下GCDAsyncSocket中与读取数据有关的几个成员的类图:
一般读取数据的流程如下:
例如通过调用readDataToData接口来读取数据:
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)maxLength
tag:(long)tag
{
//...
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:maxLength
timeout:timeout
readLength:0
terminator:data
tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) {
[readQueue addObject:packet];
[self maybeDequeueRead];
}
}});
}
- (void)maybeDequeueRead
{
if ((currentRead == nil) && (flags & kConnected)) {
if ([readQueue count] > 0) {
// Dequeue the next object in the write queue
currentRead = [readQueue objectAtIndex:0];
[readQueue removeObjectAtIndex:0];
if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) {
//...
}
else {
// Setup read timer (if needed)
[self setupReadTimerWithTimeout:currentRead->timeout];
// Immediately read, if possible
[self doReadData];
}
}
else if (flags & kDisconnectAfterReads) {
//...
}
else if (flags & kSocketSecure) {
//...
}
}
}
下面分析doReadData接口,源码中doReadData接口的代码很长,但是如果除去SSL/TLS加密的处理和一些琐碎的处理外,其实流程还是比较清晰简单的。这里不分析细节,只贴出大致的读取流程:
6.其他
其他接入如doWriteData等接口,与doReadData类似,不再赘述。
网友评论