connect 函数的调用涉及到TCP连接的三次握手过程,通常阻塞的connect 函数会等待三次握手成功或失败后返回,0成功,-1失败。如果对方未响应,要隔6s,重发尝试,可能要等待75s的尝试并最终返回超时,才得知连接失败。即使是一次尝试成功,也会等待几毫秒到几秒的时间,如果此期间有其他事务要处理,则会白白浪费时间,而用非阻塞的connect 则可以做到并行,提高效率。
而通常,非阻塞的connect 函数与 select 函数配合使用:在一个TCP套接口被设置为非阻塞之后调用connect,connect (函数本身返回-1)会立即返回EINPROGRESS或EWOULDBLOCK错误,表示连接操作正在进行中,但是仍未完成;同时TCP的三路握手操作继续进行;在这之后,我们可以调用select来检查这个链接是否建立成功。
若建立连接成功,select的结果中该描述符可写;若失败,则可写可读,此时可以使用getsockopt获取错误信息。
正常的三次握手时序:
(以下内容转自http://blog.163.com/li_xiang1102/blog/static/60714076201110298170975/)
非阻塞connect有三种用途:
1. 我们可以在三路握手的同时做一些其它的处理。connect 操作要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网,在这段时间内我们可能有一些其他的处理想要执行;
2. 可以用这种技术同时建立多个连接.在Web浏览器中很普遍;
3. 由于我们使用select 来等待连接的完,因此我们可以给select设置一个时间限制,从而缩短connect 的超时时间。在大多数实现中,connect 的超时时间在75秒到几分钟之间。有时候应用程序想要一个更短的超时时间,使用非阻塞connect 就是一种方法。
非阻塞connect 听起来虽然简单,但是仍然有一些细节问题要处理:
1. 即使套接口是非阻塞的。如果连接的服务器在同一台主机上,那么在调用connect 建立连接时,连接通常会立即建立成功,我们必须处理这种情况。
2. 源自Berkeley的实现(和Posix.1g)有两条与select 和非阻塞IO相关的规则:
A. 当连接建立成功时,套接口描述符变成可写;
B. 当连接出错时,套接口描述符变成既可读又可写。
处理非阻塞connect的步骤(重点):
1. 创建socket,返回套接口描述符;
2. 调用fcntl 把套接口描述符设置成非阻塞;
3. 调用connect 开始建立连接;
4. 判断连接是否成功建立。
判断连接是否成功建立:
A. 如果connect 返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况);
B. 调用select 来等待连接建立成功完成;
如果select 返回0,则表示建立连接超时。我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去。
如果select 返回大于0的值,则需要检查套接口描述符是否可写,如果套接口描述符可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR)。如果连接建立成功,这个错误值将是0;如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。
代码示例
int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec) { int flags, n, error, code; socklen_t len; fd_set wset; struct timeval tval; flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); error = 0; if ((n == connect(sockfd, saptr, salen)) == 0) { goto done; } else if (n < 0 && errno != EINPROGRESS){ return (-1); } /* Do whatever we want while the connect is taking place */ FD_ZERO(&wset); FD_SET(sockfd, &wset); tval.tv_sec = nsec; tval.tv_usec = 0; if ((n = select(sockfd+1, NULL, &wset, NULL, nsec ? &tval : NULL)) == 0) { close(sockfd); /* timeout */ errno = ETIMEDOUT; return (-1); } if (FD_ISSET(sockfd, &wset)) { len = sizeof(error); code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len); /* 如果发生错误,Solaris实现的getsockopt返回-1, * 把pending error设置给errno. Berkeley实现的 * getsockopt返回0, pending error返回给error. * 我们需要处理这两种情况 */ if (code < 0 || error) { close(sockfd); if (error) errno = error; return (-1); } } else { fprintf(stderr, "select error: sockfd not set"); exit(0); } done: fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ return (0); }