GCDAsyncSocket的连接,读,写过程概述

2017-01-11 15:35:48来源:http://www.jianshu.com/p/74c63d677a88作者:code道人点击

第七城市

有关于tcp的文章网上太多,这里简单说下,tcp层的传输就像一个管道,传输中的每个包都会有序的到达,比如你发送两个数据a和b,在tcp中有可能会拆成a1,a2,b1,b2。但是在接收的过程中,会以a1a2b1b2或者b1b2a1a2这两种方式到达。
下面主要说下:


(1) GCDAsyncSocket的delegate,delegateQueue,socketQueue。
(2) GCDAsyncSocket的连接过程。
(3) GCDAsyncSocket的读过程。
(4) GCDAsyncSocket的写过程。
(1)GCDAsyncSocket的delegate,delegateQueue,socketQueue。

1.delegate是用来设置代理处理各种回调。
2.delegateQueue是用来添加异步操作进行各种回调。例如添加已连接操作回调


dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToHost:host port:port];
dispatch_async(socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});

3.socketQueue是一个串行队列,确保socket操作中的每一步都是线程安全的。例如连接过程中会有这样一个同步操作


if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);

(2) GCDAsyncSocket的连接过程。

连接会有如下几个过程:
1.连接前预处理:host ,delegate, delegateQueue是否存在,是否断开连接。
2.连接过程:
2.1获取socket套接字地址sockaddr。
2.2创建socket:


socket(AF_INET/AF_INET6, SOCK_STREAM, 0)。

2.3设置socket选项:Prevent SIGPIPE signals.当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。


int nosigpipe = 1;
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));

2.4连接到上面获取到的socket地址


 connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);

2.5连接成功后进行回调处理


dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToHost:host port:port];
dispatch_async(socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});

2.6设置gcd的read/write sources进行读写事件处理。


(3) GCDAsyncSocket的读过程。

有一个类型为GCDAsyncReadPacket的currentRead维持着每一次数据的读取,也就是说,在你设置代理回调的时候,有两个地方需要创建这个GCDAsyncReadPacket的实列。两个地方如下:


- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
//第一次添加currentRead,确保数据到来能读取
[sock readDataWithTimeout:-1 tag:tag];
}

还有


- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
//上一个currentRead已被清空,需要添加一个新的,确保下次数据到来能读取。
[sock readDataWithTimeout:-1 tag:tag];
}

这是因为会把每次创建的GCDAsyncReadPacket加入一个读数组中,currentRead从这个读数组中获取。同时移除GCDAsyncReadPacket,用currentRead去读取。读取完成后(tcp管道没有数据了)就会置空currentRead。每当tcp管道中有数据到达或者需要数据过大需要分段读取的话都会触发gcd的read source事件


__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"readEventBlock");
strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable);
if (strongSelf->socketFDBytesAvailable > 0)
[strongSelf doReadData];
else
[strongSelf doReadEOF];
#pragma clang diagnostic pop
}});

(4) GCDAsyncSocket的写过程。

当你调用


- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag

这个方法的时候,同样会产生一个新的currentWrite,进行写处理。
socket调用的写函数为:


write(socketFD, buffer, (size_t)bytesToWrite);

当数据写入的数据需要分段的时候,会调用gcd的read source事件,写入完毕同时置空currentWrite。


dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"writeEventBlock");
NSLog(@"writeEventBlock");
strongSelf->flags |= kSocketCanAcceptBytes;
[strongSelf doWriteData];
#pragma clang diagnostic pop
}});

以上有不对的地方请指正。




第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台