CocoaAsyncSocket源码分析---Connect (五)
上文我们提到了
GCDAsyncSocket
的初始化,以及最终connect
之前的准备工作,包括一些错误检查;本机地址创建以及socket
创建;服务端地址的创建;还有一些本机socket
可选项的配置,例如禁止网络出错导致进程关闭的信号等言归正传,继续上文往下讲
上文讲到了本文方法八--创建Socket,其中有这么一行代码:
//和connectInterface绑定
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
//绑定失败,直接关闭返回
[self closeSocket:socketFD];
return SOCKET_NULL;
}
我们去用之前创建的本机地址去做socket
绑定,接着会调用到如下方法中:
本文方法九--给Socket绑定本机地址
//绑定一个Socket的本地地址
- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
{
// Bind the socket to the desired interface (if needed)
//无接口就不绑定,connect会自动绑定到一个不冲突的端口上去。
if (connectInterface)
{
LogVerbose(@"Binding socket...");
//判断当前地址的Port是不是大于0
if ([[self class] portFromAddress:connectInterface] > 0)
{
// Since we're going to be binding to a specific port,
// we should turn on reuseaddr to allow us to override sockets in time_wait.
int reuseOn = 1;
//设置调用close(socket)后,仍可继续重用该socket。调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程。
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
}
//拿到地址
const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
//绑定这个地址
int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
//绑定出错,返回NO
if (result != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
return NO;
}
}
//成功
return YES;
}
这个方法也非常简单,如果没有connectInterface则直接返回YES,当socket进行连接的时候,会自动绑定一个端口,进行连接。
如果有值,则我们开始绑定到我们一开始指定的地址上。
这里调用了两个和scoket相关的函数:
第一个是我们之前提到的配置scoket参数的函数:
如果有值,则我们开始绑定到我们一开始指定的地址上。
这里调用了两个和scoket相关的函数:
第一个是我们之前提到的配置scoket参数的函数:
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
这里调用这个函数的主要目的是为了调用
close
的时候,不立即去关闭socket
连接,而是经历一个TIME_WAIT
过程。在这个过程中,socket
是可以被复用的。我们注意到之前的connect
流程并没有看到复用socket
的代码。注意,我们现在走的连接流程是客户端的流程,等我们讲到服务端accept
进行连接的时候,我们就能看到这个复用的作用了。第二个是bind函数
int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
这个函数倒是很简单,就3个参数,socket
、需要绑定的地址、地址大小。这样就把socket和这个地址(其实就是端口)捆绑在一起了。
这样我们就做完了最终连接前所有准备工作,本机socket
有了,服务端的地址也有了。接着我们就可以开始进行最终连接了:
本文方法十 -- 建立连接的最终方法
//连接最终方法 3 finnal。。。
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
// If there already is a socket connected, we close socketFD and return
//已连接,关闭连接返回
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...");
}
这个方法主要就是做了一件事,调用下面一个函数进行连接:
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
这里需要注意的是这个函数是阻塞,直到结果返回之前,线程会一直停在这行。所以这里用的是全局并发队列,开辟了一个新的线程进行连接,在得到结果之后,又调回socketQueue
中进行后续操作。
如果result
为0,说明连接成功,我们会关闭掉另外一个没有用到的socket
(如果有的话)。然后调用另外一个方法做一些连接成功的初始化操作。
否则连接失败,我们会关闭socket
,填充错误并且返回。
我们下一章来看看连接成功后初始化的方法