首页 > 代码库 > 非阻塞connect
非阻塞connect
一、用途:
1.提高程序效率默认情况下connect函数是阻塞的,它发起TCP连接的三路握手。完成一个connect需要花费一个RTT时间(从本地主机到对端再回到本地),RTT的波动范围很大,局域网上几毫秒到几百毫秒,广域网上甚至能需要几秒,这对计算机来说是非常漫长的时间,这段时间可以用来执行其他的处理工作,提高效率。因此非阻塞的connect函数是必要的。
2.同时建立多个连接,不必串行的一直等待某个连接的结束,而是采用非阻塞的方式,连续建立连接,比串行更节省时间。例如,web浏览器。
3.为connect指定更短的超时时间。
connect有默认的超时时间,非阻塞connect的实现依赖于select,select的一个参数可以用来指定超时时间。
二、实现
非阻塞connect函数的实现,
1.调用fcntl将套接字设置为非阻塞
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
2.对非阻塞套接字调用connect,引发三路握手
当对一个非阻塞TCP套接字调用connect时,将返回一个EINPROGRESS错误,表示连接已经启动,但是尚未完成
connect(sockfd, (SA*)&saddr, sizeof(saddr));
3.根据select判断连接结果
当连接建立时,描述符变为可写(因为并未发送数据,套接字发送缓冲区有可用空间)
当连接建立遇到错误时,描述符变为即可读,又可写(一个套接字上发生某个错误,待处理的错误总是导致该套接字变为既可读又可写),当连接建立成功,并且来自对端的数据到达时,描述符也变为既可读又可写,所以这里通过getsockopt取得套接字待处理的错误(使用SO_ERROR套接字选项)。连接成功建立,该值为0,否则为对应的错误值。
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { socklen_t len = sizeof(error); /*SO_ERROR 获取错误状态*/ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { close(sockfd); return -1; } } if (error) { close(sockfd); return -1; } }
示例程序
/* *connect_nowait 非阻塞connect建立连接并返回连接好的套接字 *addr 连接到的ipv4地址 *port 连接到的端口 *nsec 超时时间 *fp 函数指针 *返回值:成功返回套机字,出错返回-1 */ int connect_nowait(const char* addr,const unsigned int port, const int nsec, void (*fp)()) { int sockfd; struct sockaddr_in saddr; int flags; int r; int error; fd_set rset, wset; struct timeval tval; if (addr == NULL) return -1; /*地址格式不合法*/ if (inet_addr(addr) == -1) return -1; /*端口超出范围*/ if (port > 65535) return -1; /*时间不合法*/ if (nsec < 0) return -1; if (fp == NULL) return -1; bzero((void*)&saddr, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = inet_addr(addr); saddr.sin_port = htons(port); /*socket异常*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; if ((flags = fcntl(sockfd, F_GETFL, 0)) == -1) { close(sockfd); return -1; } /*设置为非阻塞*/ if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { close(sockfd); return -1; } /*对非阻塞的TCP套接字调用connect,期待返回EINPROGRESS错误,表示引发三路握手,但连接尚未完成*/ if ((r = connect(sockfd, (SA*)&saddr, sizeof(saddr))) == -1) { if (errno != EINPROGRESS) { close(sockfd); return -1; } } /*建立连接期间,这里可以做一些其他的处理,提高时间利用率*/ fp(); /*如果客户-服务器都在一台主机上,那么这里可能会立即建立连接*/ if (r == 0) goto done; /*将套接字添加到读写描述符集中*/ FD_ZERO(&rset); FD_SET(sockfd, &rset); wset = rset; /*初始化超时时间*/ tval.tv_sec = nsec; tval.tv_usec = 0; /*select 异常*/ if ((r = select(sockfd+1, &rset, &wset, NULL, &tval)) == -1) { close(sockfd); return -1; } /*select 超时*/ if (r == 0) { close(sockfd); return -1; } /* *三种情况 *1.套接字成功建立连接,此时可写 *2.连接出错,套接字既可读又可写 *3.成功建立连接,并有对端数据到达,套接字既可读又可写 */ if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { socklen_t len = sizeof(error); /*SO_ERROR 获取错误状态*/ if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { close(sockfd); return -1; } } if (error) { close(sockfd); return -1; } done: if ((r = fcntl(sockfd, F_SETFL, flags)) == -1) { close(sockfd); return -1; } return sockfd; }
#include <iostream> #include "net.h" using namespace std; void while_connect() { for(int i=0; i<10; i++) cout << "while_connect" << endl; } int main() { int sockfd; char *addr = "182.92.78.165"; unsigned int port = 33333; int nsec = 3; sockfd = connect_nowait(addr, port, nsec, while_connect); if (sockfd == -1) cout << "连接失败" << endl; cout << "连接成功" << endl; return 0; }
执行结果:
总结:
非阻塞connect实际上与普通建立套接字并连接的步骤没有太大差别,只是调用fcntl将套接字设置为非阻塞后调用connect,同样引发三路握手,并不等待connect返回,,而去利用这段时间处理其他操作,更好的利用时间,之后再利用select检测套接字来判断连接是否完成,并且可以指定一个超时时间。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。