非阻塞connect
TCP连接的建立涉及到一个三次握手的过程,且SOCKET中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回,这意味着每个connect函数总会阻塞其调用进程至少一个到服务器的RTT时间,而RTT波动范围很大,从局域网的几个毫秒到几百个毫秒甚至广域网上的几秒。这段时间内,我们可以执行其他处理工作,以便做到并行。在此,需要用到非阻塞connect。
非阻塞connect的作用:
- 可以让三路握手的处理等同与一般数据的处理,而不是一直让
connect
一直尝试重连或者花费一个RTT
时间。而且RTT时间从几毫秒到几秒不等,万一有许多连接,不论是尝试重连还是花费一个RTT时间,都将是致命的延时。
- 可以使用该技术同时建立多个连接。Web浏览器中常用。
- 既然使用
select
等待连接的建立,我们就可以制定一个时间限制,使得我们能够缩短connect
的超时。
使用selcet
与非阻塞connect
的一些注意事项:
- 处理
connect
立即建立的情况。(比如我们连接的是同一个主机时)
- 当连接建立成功时,套接口描述符变成可写;
- 当一个套接口出错时,它会被 select 调用标记为既可读又可写
处理非阻塞 connect的步骤:
第一步,创建 socket,返回套接字描述符;
第二步,调用 fcntl 或 ioctlsocket 把套接口描述符设置成非阻塞;
第三步,调用 connect 开始建立连接;
第四步,判断连接是否成功建立:
- 如果 connect 返回 0 ,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况);
- 调用select 来等待连接建立成功完成;
如果select 返回0,则表示建立连接超时。我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去。
如果select 返回大于0的值,则需要检查套接口描述符是否可写,如果套接口描述符可写,则我们可以通过调用getsockopt来得到 套接口上待处理的错误(SO_ERROR)。如果连接建立成功,这个错误值将是0;如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。
移植性问题总结 :
- 对于出错的套接口描述符,getsockopt 的返回值源自 Berkeley 的实现是返回 0 ,待处理的错误值存储在 errno 中;而源自 Solaris 的实现是返回 -1 ,待处理的错误存储在 errno 中。(套接口描述符出错时调用 getsockopt 的返回值不可移植)
- 有可能在调用 select 之前,连接就已经建立成功,而且对方的数据已经到来,在这种情况下,套接口描述符是既可读又可写,这与套接口描述符出错时是一样的。(怎样判断连接是否建立成功的条件不可移植)
这样的话,在我们判断连接是否建立成功的条件不唯一时,我们可以有以下的方法来解决这个问题:
- 调用获取对端socket地址的 getpeername 代替 getsockopt 。如果调用 getpeername 失败,getpeername 返回 ENOTCONN ,表示连接建立失败,之后我们必须再以 SO_ERROR 调用 getsockopt 得到套接口描述符上的待处理错误;
- 调用 read ,读取长度为 0 字节的数据。如果连接建立失败,则 read 会返回 -1 ,且相应的 errno 指明了连接失败的原因;如果连接建立成功,read 应该返回 0 。
- 再调用一次 connect 。它应该失败,如果错误 errno 是 EISCONN ,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include"head.h"
using namespace std;
#define BUFFER_SIZE 1024
int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; }
int unblock_connect(const char* ip, int port, int time) { int ret = 0; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port);
int sockfd = socket(AF_INET, SOCK_STREAM, 0); int fdopt = setnonblocking(sockfd); ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address)); if(ret == 0) { printf("connect with server immediately\n"); fcntl(sockfd, F_SETFL, fdopt); return sockfd; } else if(errno != EINPROGRESS) { printf("unblock connect not support\n"); return -1; } fd_set readfds; fd_set writefds; struct timeval timeout;
FD_ZERO(&readfds); FD_ZERO(&writefds);
timeout.tv_sec = time; timeout.tv_usec = 0;
ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout); if(ret <= 0) { printf("connection time out\n"); close(sockfd); return -1; }
if(!FD_ISSET(sockfd, &writefds)) { printf("no events on sockfd found\n"); close(sockfd); return -1; }
int error = 0; socklen_t length = sizeof(error); if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0) { printf("get socket option failed\n"); close(sockfd); return -1; }
if(errno != 0) { printf("connection failed after select with the error: %d\n", error); close(sockfd); return -1; }
printf("connection ready after select with the socket: %d\n", sockfd); fcntl(sockfd, F_SETFL, fdopt); return sockfd; }
int main(int argc, char** argv) { if(argc <= 2) { printf("usage: %s ip_address port_number\n", basename(argv[0])); return 1; } const char* ip = argv[1]; int port = atoi(argv[2]); int sockfd = unblock_connect(ip, port, 10); if(sockfd < 0) return 1; close(sockfd); return 0; }
|