iOS GCDAsyncSocket源码分析(一)
2019-04-02 本文已影响81人
尤先森
序言
在上一篇文章文章中,简单介绍了GCDAsyncSocket的使用,socket创建、连接、发送消息、接收消息、关闭socket、粘包分包、以及心跳包机制。并且立下了一个flag,所以在这篇文章,将带来GCDAsyncSocket的源码分析,看看在GCDAsyncSocket中是如何运用原生代码并封装起来的,在简单实现的原生代码基础上,他又做了什么样的操作。
我们还是按照创建socket、连接socket、发送消息、接收消息、关闭socket的顺序,一步一步深入了解GCDAsyncSocket。
1. GCDAsyncSocket初始化
在开始之前,GCDAsyncSocket.m中声明了许许多多的成员变量,先看看都是啥。
@implementation GCDAsyncSocket
{
//flags,当前正在做操作的标识符
uint32_t flags;
uint16_t config;
//代理
__weak id<GCDAsyncSocketDelegate> delegate;
//代理回调的queue
dispatch_queue_t delegateQueue;
//本地IPV4Socket
int socket4FD;
//本地IPV6Socket
int socket6FD;
//unix域的套接字 // 进程通讯 locahost VS 127.0.0.1
int socketUN;
//unix域 服务端 url
NSURL *socketUrl;
//状态Index
int stateIndex;
//本机的IPV4地址 --- 地址host interface
NSData * connectInterface4;
//本机的IPV6地址
NSData * connectInterface6;
//本机unix域地址
NSData * connectInterfaceUN;
//这个类的对Socket的操作都在这个queue中,串行
dispatch_queue_t socketQueue;
// 源 ---> mergdata get_data buffer tls ssl CFStream
// data
dispatch_source_t accept4Source;
dispatch_source_t accept6Source;
dispatch_source_t acceptUNSource;
//连接timer,GCD定时器 重连
dispatch_source_t connectTimer;
dispatch_source_t readSource;
dispatch_source_t writeSource;
dispatch_source_t readTimer;
dispatch_source_t writeTimer;
//读写数据包数组 类似queue,最大限制为5个包 - FIFO
NSMutableArray *readQueue;
NSMutableArray *writeQueue;
//当前正在读写数据包
GCDAsyncReadPacket *currentRead;
GCDAsyncWritePacket *currentWrite;
//当前socket未获取完的数据大小
unsigned long socketFDBytesAvailable;
//全局公用的提前缓冲区
GCDAsyncSocketPreBuffer *preBuffer;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
//读的数据流 ---- c
CFReadStreamRef readStream;
//写的数据流
CFWriteStreamRef writeStream;
#endif
//SSL上下文,用来做SSL认证
SSLContextRef sslContext;
//全局公用的SSL的提前缓冲区
GCDAsyncSocketPreBuffer *sslPreBuffer;
size_t sslWriteCachedLength;
//记录SSL读取数据错误
OSStatus sslErrCode;
//记录SSL握手的错误
OSStatus lastSSLHandshakeError;
//socket队列的标识key -- key - queue
void *IsOnSocketQueueOrTargetQueueKey;
id userData;
//连接备选服务端地址的延时 (另一个IPV4或IPV6)
NSTimeInterval alternateAddressDelay;
}
创建函数
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
这个init方法最终将会来到,在这个方法里,socketQueue传值为NULL,所以后面如果有sq的部分可以先行跳过,等梳理完了,再去看看这个sq具体都干了啥。
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
//这个宏是在sdk6.0之后才有的,如果是之前的,则OS_OBJECT_USE_OBJC为0,!0即执行if语句
//对6.0的适配,如果是6.0以下,则去retain release,6.0之后ARC也管理了GCD
//作者很细
#if !OS_OBJECT_USE_OBJC
if (dq) dispatch_retain(dq);
#endif
//创建socket,先都置为 -1 , 代表socket默认创建失败
//本机的ipv4
socket4FD = SOCKET_NULL;
//ipv6
socket6FD = SOCKET_NULL;
//应该是UnixSocket
socketUN = SOCKET_NULL;
//url
socketUrl = nil;
//状态
stateIndex = 0;
//这里并没有sq,可以选择跳过
if (sq)
{
//如果scoketQueue是global的,则报错。断言必须要一个非并行queue。
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
//拿到scoketQueue
socketQueue = sq;
//iOS6之下retain
#if !OS_OBJECT_USE_OBJC
dispatch_retain(sq);
#endif
}
else
{
//没有的话创建一个socketQueue, 名字为:GCDAsyncSocket,NULL = 串行
socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
}
//比如原来为 0X123 -> NULL 变成 0X222->0X123->NULL
//自己的指针等于自己原来的指针,成二级指针了 看了注释是为了以后省略&,让代码更可读?
//这里不懂作者的用意,继续往下看
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
//dispatch_queue_set_specific给当前队里加一个标识 dispatch_get_specific当前线程取出这个标识,判断是不是在这个队列
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
//读的数组
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
//写的数组
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
//缓冲区 设置大小为 4kb
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
#pragma mark alternateAddressDelay??
//交替地址延时?? wtf 应该是用来给备用地址的
alternateAddressDelay = 0.3;
}
return self;
}
看完这段代码...懵逼。只是一些初始化操作。本来还以为create()
会在这里面呢,很无奈啊,哎,先不管了,继续往下看吧。
2. GCDAsyncSocket Connect
外层调用
[self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
底层最终会来到这里,每个方法都好长啊 - - 。这里的inInterface传入的是nil,所以,跟上面那个方法的sq一样,如果有遇到可以选择跳过。
- (BOOL)connectToHost:(NSString *)inHost
onPort:(uint16_t)port
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
//LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -- 跟踪当前行为
LogTrace();
//拿到host ,copy防止值被修改
NSString *host = [inHost copy];
//interface?接口?先不管 反正是nil
NSString *interface = [inInterface copy];
//声明两个__block的临时变量
__block BOOL result = NO;
//error信息
__block NSError *preConnectErr = nil;
//gcdBlock ,都包裹在自动释放池中 :
// 1: 大量临时变量 connect : 重连
// 2: 自定义线程管理 : nsoperation
// 3: 非UI 命令 工具
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
// 翻译:检查host参数 是否存在问题
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
preConnectErr = [self badParamError:msg];
// 其实就是return,大牛的代码真是充满逼格 - ret
// 里面有注释,有想法的可以自己去看看,大概意思就是
// 可以让这个return能更快的被read,后面还有很多地方被调用到
return_from_block;
}
//一个前置的检查,如果没通过返回,这q个检查里,如果interface有值,则会将本机的IPV4 IPV6的 address设置上。
// 参数 : 指针 操作同一片内存空间
// 因为interface 是nil,所以不会执行return
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
return_from_block;
}
// We've made it past all the checks.我们已经检查了所有参数
// It's time to start the connection process.是时候开始连接了
//flags 做或等运算。 flags标识为开始Socket连接
flags |= kSocketStarted;
//又是一个{}? 只是为了标记么?
LogVerbose(@"Dispatching DNS lookup...");
//很可能给我们的服务端的参数是一个可变字符串
//所以我们需要copy,在Block里同步的执行
//这种基于Block的异步查找,不需要担心它被改变
//copy,防止改变
NSString *hostCpy = [host copy];
//拿到状态 初始化的时候 stateIndex = 0
int aStateIndex = stateIndex;
__weak GCDAsyncSocket *weakSelf = self;
//获取全局并发Queue
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//异步执行,这里的autoreleasepool 跟上面的一样,可以往上翻
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
//忽视循环引用,牛逼
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//查找错误
NSError *lookupErr = nil;
//server地址数组(包含IPV4 IPV6的地址 sockaddr_in6、sockaddr_in类型)
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
//strongSelf
__strong GCDAsyncSocket *strongSelf = weakSelf;
//完整Block安全形态,在加个if
if (strongSelf == nil) return_from_block;
//如果有错
if (lookupErr)
{
//用cocketQueue
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//一些错误处理,清空一些数据等等
[strongSelf lookup:aStateIndex didFail:lookupErr];
}});
}
//正常
else
{
NSData *address4 = nil;
NSData *address6 = nil;
//遍历地址数组
for (NSData *address in addresses)
{
//判断address4为空,且address为IPV4
if (!address4 && [[self class] isIPv4Address:address])
{
address4 = address;
}
//判断address6为空,且address为IPV6
else if (!address6 && [[self class] isIPv6Address:address])
{
address6 = address;
}
}
//异步去发起
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
// 方法名大概是说,address4 address6 两个地址都成功获取到了。
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
}
#pragma clang diagnostic pop
}});
//开启连接超时
[self startConnectTimeout:timeout];
result = YES;
}};
//在socketQueue中执行这个Block
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
//否则同步的调起这个queue去执行
else
dispatch_sync(socketQueue, block);
//如果有错误,赋值错误
if (errPtr) *errPtr = preConnectErr;
//把连接是否成功的result返回
return result;
}
这个connect跟想的也不太一样,并没有熟悉的connect()
,有毒。但是!还知道这个方法里都干了啥呢。
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
一探究竟!
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//至少有一个server地址
NSAssert(address4 || address6, @"Expected at least one valid address");
//如果状态不一致,说明断开连接
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
// Check for problems
//分开判断。
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address6 == nil))
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
[self closeWithError:[self otherError:msg]];
return;
}
if (isIPv6Disabled && (address4 == nil))
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
[self closeWithError:[self otherError:msg]];
return;
}
// Start the normal connection process
NSError *err = nil;
//调用连接方法,如果失败,则错误返回
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[self closeWithError:err];
}
}
咦,好像有点苗头,看作者悄咪咪的都干了些啥。
if (![self connectWithAddress4:address4 address6:address6 error:&err])
继续点进去看看
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
//输出了两个地址的信息
LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
//判断是否倾向于IPV6
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
// Create and bind the sockets
//如果有IPV4地址
if (address4)
{
LogVerbose(@"Creating IPv4 socket");
// 咦?这不是创建吗,瞧瞧我发现了啥。
socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
}
//如果有IPV6地址,同上
if (address6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
}
//如果都为空,直接返回
if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
{
return NO;
}
//主选socketFD,备选alternateSocketFD
int socketFD, alternateSocketFD;
//主选地址和备选地址
NSData *address, *alternateAddress;
//IPV6
if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
{
socketFD = socket6FD;
alternateSocketFD = socket4FD;
address = address6;
alternateAddress = address4;
}
//主选IPV4
else
{
socketFD = socket4FD;
alternateSocketFD = socket6FD;
address = address4;
alternateAddress = address6;
}
//拿到当前状态
int aStateIndex = stateIndex;
// 我去,这不是连接吗?都悄咪咪的把创建跟连接放在这个方法里了,糟老头子坏得很。
[self connectSocket:socketFD address:address stateIndex:aStateIndex];
//如果有备选地址
if (alternateAddress)
{
//延迟去连接备选的地址
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
[self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
});
}
return YES;
}
作者是真的皮啊,把这么重要的方法,放在一个if里面?骚还是你骚啊。
总算是找到创建跟连接了,说什么也要点进去看看吧。
先看创建
//创建Socket
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
// 注意
// 这个connectInterface 创建socketFD4跟6时,分别是传入了connectInterface4与connectInterface6
// 这两个值,在preConnectWithInterface时,如果interface不为空,就会赋值,但是interface一直是nil,所以
// connectInterface4与connectInterface6 都是nil
// 创建socket,用的SOCK_STREAM TCP流
// 总算是看到了熟悉的东西
int socketFD = socket(family, SOCK_STREAM, 0);
//如果创建失败 SOCKET_NULL = -1
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return socketFD;
}
//和connectInterface绑定,由于connectInterface 是nil 所以这个方法会放回YES,
//所以不会走进去
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
//绑定失败,直接关闭返回
[self closeSocket:socketFD];
return SOCKET_NULL;
}
// Prevent SIGPIPE signals
//防止终止进程的信号?
int nosigpipe = 1;
//SO_NOSIGPIPE是为了避免网络错误,而导致进程退出。用这个来避免系统发送signal
//setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。百度百科有详解
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
return socketFD;
}
再来就是连接socket
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
//已连接,关闭连接返回
if (self.isConnected)
{
[self closeSocket:socketFD];
return;
}
// Start the connection process in a background queue
//开始连接过程,在后台queue中
__weak GCDAsyncSocket *weakSelf = self;
//获取到全局Queue
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//新线程
dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
//调用connect方法,该函数阻塞线程,所以要异步新线程
//客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
//老样子,安全判断
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
//在socketQueue中,开辟线程
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
//如果状态为已经连接,关闭连接返回
if (strongSelf.isConnected)
{
[strongSelf closeSocket:socketFD];
// 又是这个装逼写法
return_from_block;
}
//说明连接成功
if (result == 0)
{
//关闭掉另一个没用的socket
[self closeUnusedSocket:socketFD];
//调用didConnect,生成stream,改变状态等等!
[strongSelf didConnect:aStateIndex];
}
//连接失败
else
{
//关闭当前socket
[strongSelf closeSocket:socketFD];
// If there are no more sockets trying to connect, we inform the error to the delegate
//返回连接错误的error
if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
{
NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
[strongSelf didNotConnect:aStateIndex error:error];
}
}
}});
#pragma clang diagnostic pop
});
//输出正在连接中
LogVerbose(@"Connecting...");
}
至此,我们就看到了socket的创建跟连接的实现原理,接下来说读写操作。
由于篇幅问题这里另起一篇文章看这里