非阻塞connect编写方法介绍

来源:互联网 发布:淘宝网店怎么装修视频 编辑:程序博客网 时间:2024/06/07 06:13
TCP连接的建立涉及到一个三次握手的过程,且SOCKET中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回,这意味着每个connect函数总会阻塞其调用进程至少一个到服务器的RTT时间,而RTT波动范围很大,从局域网的几个毫秒到几百个毫秒甚至广域网上的几秒。这段时间内,我们可以执行其他处理工作,以便做到并行。在此,需要用到非阻塞connect。本文主要介绍了非阻塞connect的编写方法以及应用场景。

1. 基础知识

(1) fcntl函数

fcntl函数可执行各种描述符的控制操作,对于socket描述符,常用应用是将其设置为阻塞式IO,代码如下:

int flags;if((flags = fcntl(fd, F_GETFL)) < 0) //获取当前的flags标志    err_sys(“F_GETFL error!”);flags |= O_NONBLOCK; //修改非阻塞标志位if(fcntl(fd, F_SETFL, flags) < 0)    err_sys(“F_SETFL error!”);

(2) connect函数

对于阻塞式套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或者出错时才返回;对于非阻塞式套接字,如果调用connect函数会之间返回-1(表示出错),且错误为EINPROGRESS,表示连接建立,建立启动但是尚未完成;如果返回0,则表示连接已经建立,这通常是在服务器和客户在同一台主机上时发生。


 
if (connect(fd, (struct sockaddr*)&sa, sizeof(sa)) == -1)   if (errno != EINPROGRESS) {       return -1;   }   if(n == 0)       goto done;

(3) select函数

select是一种IO多路复用机制,它允许进程指示内核等待多个事件的任何一个发生,并且在有一个或者多个事件发生或者经历一段指定的时间后才唤醒它。

connect本身并不具有设置超时功能,如果想对套接字的IO操作设置超时,可使用select函数。

fd_set wfd; FD_ZERO(&wfd); FD_SET(fd, &wfd); if(select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {       __redisSetError(c, REDIS_ERR_IO, sdscatprintf(sdsempty(),"select(2): %s",strerror(errno)));       close(fd);       return REDIS_ERR;} 
对于select和非阻塞connect,注意两点:

[1] 当连接成功建立时,描述符变成可写; [2] 当连接建立遇到错误时,描述符变为即可读,也可写,遇到这种情况,可调用getsockopt函数。

(4) getsockopt函数

可获取影响套接字的选项,比如SOCKET的出错信息:

err = 0;errlen = sizeof(err);if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {   sprintf("getsockopt(SO_ERROR): %s", strerror(errno)));   close(fd);   return ERR;} if (err) {   errno = err;   close(fd);   return ERR;}

2. 实现非阻塞式connect

分以下几步:

(1) 创建socket,并利用fcntl将其设置为非阻塞

(2) 调用connect函数,如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。

(3) 为了控制连接建立时间,将该socket描述符加入到select的可写集合中,采用select函数设定超时。

(4) 如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息

(5) 恢复套接字的文件状态并返回。

3. 应用实例

(1)实例一

《unix网络编程》卷1的16.5节有一个Netscape 的web客户端的程序实例,客户端先建立一个与某个web服务器的HTTP连接,然后获取该网站的主页。该主页往往含有多个对于其他网页的引用,客户可以使用非阻塞connect同时获取多个网页,以此取代每次只获取一个网页的串行获取手段。

(2)实例二

Redis客户端CLI (command line interface),位于源代码的src/deps/hiredis下面。实际上,不仅是Redis客户端,其他类似的client/server架构中,client均可采用非阻塞式connect实现。

int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {    int s;    int blocking = (c->flags & REDIS_BLOCK);    struct sockaddr_in sa;     if ((s = redisCreateSocket(c,AF_INET)) < 0)        return REDIS_ERR;    if (redisSetBlocking(c,s,0) != REDIS_OK)        return REDIS_ERR;    sa.sin_family = AF_INET;    sa.sin_port = htons(port);    if (inet_aton(addr, &sa.sin_addr) == 0) {        struct hostent *he;    he = gethostbyname(addr);    if (he == NULL) {        __redisSetError(c,REDIS_ERR_OTHER,  sdscatprintf(sdsempty(),"Can't resolve: %s",addr));        close(s);        return REDIS_ERR;    }    memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));     if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {        if (errno == EINPROGRESS && !blocking) {        /* This is ok. */    } else {        if (redisContextWaitReady(c,s,timeout) != REDIS_OK)            return REDIS_ERR;    }     /* Reset socket to be blocking after connect(2). */    if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)        return REDIS_ERR;     if (redisSetTcpNoDelay(c,s) != REDIS_OK)        return REDIS_ERR;     c->fd = s;    c->flags |= REDIS_CONNECTED;    return REDIS_OK;} static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {    struct timeval to;    struct timeval *toptr = NULL;    fd_set wfd;    int err;    socklen_t errlen;  /* Only use timeout when not NULL. */    if (timeout != NULL) {        to = *timeout;        toptr = &to;    }     if (errno == EINPROGRESS) {        FD_ZERO(&wfd);        FD_SET(fd, &wfd);         if (select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {            __redisSetError(c,REDIS_ERR_IO,            sdscatprintf(sdsempty(), "select(2): %s", strerror(errno)));            close(fd);            return REDIS_ERR;        }         if (!FD_ISSET(fd, &wfd)) {            errno = ETIMEDOUT;            __redisSetError(c,REDIS_ERR_IO,NULL);            close(fd);            return REDIS_ERR;        }         err = 0;        errlen = sizeof(err);        if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {            __redisSetError(c,REDIS_ERR_IO,            sdscatprintf(sdsempty(), "getsockopt(SO_ERROR): %s", strerror(errno)));            close(fd);            return REDIS_ERR;        }         if (err) {            errno = err;            __redisSetError(c,REDIS_ERR_IO,NULL);            close(fd);            return REDIS_ERR;        }    return REDIS_OK;}  __redisSetError(c,REDIS_ERR_IO,NULL);   close(fd);   return REDIS_ERR;}
0 0
原创粉丝点击