非阻塞 connect: 在一个 TCP 套接字被设置为非阻塞之后调用 connect ,connect 会立即返回 EINPROGRESS 错误,表示连接操作正在进行中,但是仍未完成,与此同时 TCP 三次握手操作会同时进行。在这之后,我们可以通过调用 select 来检查这个链接是否建立成功。
非阻塞 connect 有三种用途:
1.我们可以在 TCP 三次握手的同时做一些其它的处理。connect 操作需要一个往返时间才能完成,从几个毫秒(局域网)到几百毫秒或几秒(广域网)。在这段时间内我们可能有一些其他的处理想要同时执行;
2.可以用这种技术同时建立多个连接。在 Web 浏览器中很普遍;
3.由于我们使用 select 来等待连接的完成,因此我们可以给 select 设置一个时间限制,从而缩短 connect 的超时时间。在大多数实现中,connect 的超时时间在 75 秒到几分钟之间。有时候应用程序想要一个更短的超时时间,使用非阻塞 connect 就是一种方法。
非阻塞 connect 听起来虽然简单,但是仍然有一些细节问题要处理:
1.即使套接字是非阻塞的,如果连接的服务器在同一台主机上,那么在调用 connect 建立连接时,连接通常会立即建立成功。我们必须处理这种情况;
2.源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:
A)当连接建立成功时,套接口描述符变成 可写;
B)当连接建立出错时,套接口描述符变成 既可读又可写;
注意:当一个套接口出错时,它会被 select 调用标记为既可读又可写。
非阻塞 connect 有这么多好处,但是处理非阻塞 connect 时会遇到很多【可移植性问题】。
处理非阻塞 connect 的步骤:
第一步,创建 socket,返回套接字描述符;
第二步,调用 fcntl 或 ioctlsocket 把套接口描述符设置成非阻塞;
第三步,调用 connect 开始建立连接;
第四步,判断连接是否成功建立:
A)如果 connect 返回 0 ,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况);
B)调用 select 来判定连接建立的是否成功;
如果 select 返回 0 ,则表示在 select 的超时时间内未能成功建立连接;我们需要返回超时错误给用户,同时关闭连接,以防止TCP三次握手继续进行下去;
如果 select 返回大于 0 的值,则说明检测到可读或可写或异常的套接字描述符存在;此时我们可以通过调用 getsockopt 来检测集合中的套接口上是否存在待处理的错误,如果连接建立是成功的,则通过 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的 error 值将是 0 ,如果建立连接时遇到错误,则 error 的值是连接错误所对应的 errno 值,比如ECONNREFUSED,ETIMEDOUT等。
实例代码:
BOOL ConnectServer() { SOCKET sClient; int ret; struct sockaddr_in server; sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sClient == INVALID_SOCKET) { return FALSE; } // set Recv and Send time out int TimeOut = 15000; // 设置发送超时15秒 if (setsockopt(sClient, SOL_SOCKET, SO_SNDTIMEO, (char *)&TimeOut, sizeof(TimeOut)) == SOCKET_ERROR) { closesocket(sClient); return FALSE; } if (setsockopt(sClient, SOL_SOCKET, SO_RCVTIMEO, (char *)&TimeOut, sizeof(TimeOut)) == SOCKET_ERROR) { closesocket(sClient); return FALSE; } unsigned long ul = 1; ret = ioctlsocket(sClient, FIONBIO, (unsigned long *)&ul); if (ret == SOCKET_ERROR) { closesocket(sClient); return FALSE; } server.sin_family = AF_INET; server.sin_port = htons(80); server.sin_addr.s_addr = inet_addr(“192.168.100.22”); if (server.sin_addr.s_addr == INADDR_NONE) { closesocket(sClient); return FALSE; } BOOL bConnRe = FALSE; if (connect(sClient, (struct sockaddr *)&server, sizeof(server)) == -1) { struct timeval timeout_val; fd_set set; FD_ZERO(&set); FD_SET(sClient, &set); timeout_val.tv_sec = 5; timeout_val.tv_usec = 0; if(select(0, NULL, &set, NULL, &timeout_val) > 0) { bConnRe = TRUE; } else bConnRe = FALSE; } else bConnRe = TRUE; if (!bConnRe) { closesocket(sClient); return FALSE; } unsigned long ull = 0; ret = ioctlsocket(sClient, FIONBIO, (unsigned long *)&ull); if (ret == SOCKET_ERROR) { closesocket(sClient); return FALSE; } m_sock = sClient; return TRUE; }
1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思?有没有区别?(就我目前了解阻塞与非阻塞recv返回值没有区分,都是
<0:出错,=0:连接关闭,>0接收到数据大小,特别:返回值 <0时并且(errno == EINTR || errno ==
EWOULDBLOCK || errno ==
EAGAIN)的情况下认为连接是正常的,继续接收。只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取)。
2、阻塞模式与非阻塞模式下write的返回值各代表什么意思?有没有区别?(就我目前了解阻塞与非阻塞write返回值没有区分,都是
<0:出错,=0:连接关闭,>0发送数据大小,特别:返回值 <0时并且(errno == EINTR || errno ==
EWOULDBLOCK || errno ==
EAGAIN)的情况下认为连接是正常的,继续发送。只是阻塞模式下write会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着
write,因此需要循环发送)。