connect(3) - Linux man page
If the initiating socket is connection-mode, then connect() shall attempt to establish a connection to the address specified by the address argument. If the connection cannot be established immediately and O_NONBLOCK is not set for the file descriptor for the socket, connect() shall block for up to an unspecified timeout interval until the connection is established.
如果阻塞调用connect建立tcp,当无法立即建立连接的话,connect会一直阻塞到连接建立或一个调用都没有指定超时时间的定时器超时。这种时间不确定的阻塞可能在某些情况下不适用,我们需要一个可以指定超时时间的connect。这里的定时器应该是由系统协议栈默认指定的,目前还没学习到~ 而且系统应该也应该提供修改的sysctl接口,不过如果为了一个进程修改系统参数显然不是明智的选择。
I. connect函数
TCP连接的建立涉及到一个三次握手的过程,且SOCKET中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回,这意味着每个connect函数总会阻塞其调用进程至少一个到服务器的RTT时间,而RTT波动范围很大,从局域网的几个毫秒到几百个毫秒甚至广域网上的几秒。
1. 对于阻塞式TCP套接字,调用connect函数将激发三次握手过程,而且仅在连接建立成功或者出错时(超时,信号中断等)才返回;
2. 对于非阻塞式套接字,如果调用connect函数会之间返回-1(表示出错),且错误为EINPROGRESS,表示开始建立连接但是尚未完成;如果返回0,则表示连接已经建立,这通常是在服务器和客户在同一台主机上时发生。
II. select函数
connect需要依靠select实现指定的超时时间。select是一种IO多路复用机制,它允许进程指示内核等待多个事件的任何一个发生,并且在有一个或者多个事件发生或者经历一段指定的时间后才唤醒它。当连接成功建立时,描述符变成可写; 当连接建立遇到错误时,描述符变为即可读可写。所以可以直接调用getsockopt函数判断连接的成功与否。
III. 实现步骤
- 创建socket,并利用fcntl将其设置为非阻塞;
- 调用connect函数,如果返回0,则连接成功;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立;
- 将该socket描述符加入到select的读写集合中,用select函数设定超时;
- 如果规定时间内select返回正值,通过getsockopt在SOL_SOCKET级别上读取SO_ERROR选项的取值来判断连接是否成功;
- 恢复套接字的文件状态并返回。
IV. 参考代码
//################################
//可以指定超时时间的connect,成功返回0
//################################
int
connect_with_timeout(const char *host, const short port, int seconds, int sockfd)
{
struct addrinfo hints;
struct addrinfo *ai = NULL;
struct timeval tv;
fd_set rset, wset;
int flags = 0;
int iRet = 0;
int len, error;
// 1. Set non-blocking
flags = fcntl(sockfd, F_GETFL, NULL);
fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
// 2. Trying to connect
bzero(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
iRet = getaddrinfo(host, port, &hints, &ai);
if (0 != iRet){
fprintf(stderr, "getaddrinfo return failed,return!\n");
goto RET;
}
iRet = connect(sockfd, ai->ai_addr, ai->ai_addrlen);
if (0 == iRet)
goto RET;
else
if (EINPROGRESS != errno) {
fprintf(stderr, "connect failed without EINPROGRESS, return\n");
goto RET;
}
//3. 加入select
tv.tv_sec = seconds;
tv.tv_usec = 0;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(sockfd, &rset);
FD_SET(sockfd, &wset);
iRet = select(sockfd + 1, &rset, &wset, NULL, &tv);
if (iRet <= 0) {
fprintf(stderr, "Error select %d - %s\n", errno, strerror(errno));
iRet = -1;
goto RET;
}
len = sizeof(int);
iRet = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if (iRet < 0) {
fprintf(stderr, "Error in getsockopt() %d - %s\n", errno, strerror(errno));
goto RET;
}
else
if (0 != error){
iRet = -1;
fprintf(stderr, "Error in asynchronous connect\n");
}
RET:
if (flags)
fcntl(sockfd, F_SETFL, flags);
if(ai)
freeaddrinfo(ai);
return iRet;
}