I/O复用的高级应用一:非阻塞connect

来源:互联网 发布:游族网络002174股吧 编辑:程序博客网 时间:2024/04/30 15:35

I/O复用的高级应用一:非阻塞connect

  在终端输入man connect,展示的内容有如下一段:

EINPROGRESS    The  socket  is  nonblocking  and  the connection cannot be completed immediately.  It is possible to select(2) or poll(2) for completion by selecting the socket for writing.  After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or  unsuccessfully  (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).

  这段话描述了connect出错时的一种errno值:EINPROGRESS。这种错误发生在对非阻塞的socket调用connect,而连接又没有立即建立时。根据man手册的解释,在这种情况下,我们可以调用select、poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功建立,否则连接失败。

  通过上面描述的非阻塞connect方式,我们就能同时发起多个连接并一起等待。下面看看非阻塞connect的一种实现:

#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdlib.h>#include <assert.h>#include <stdio.h>#include <time.h>#include <errno.h>#include <fcntl.h>#include <sys/ioctl.h>#include <unistd.h>#include <string.h>#define BUFFER_SIZE 1023int 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;}/* 超时连接函数,参数分别是服务器IP地址、端口号和超时时间(秒)。函数成功时返回已经处于连接状态的socket,失败时则返回-1 */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(PF_INET, SOCK_STREAM, 0);    int fdopt = setnonblocking(sockfd);    ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));    if(ret == 0)    {        /* 如果连接成功,则恢复sockfd的属性,并立即返回之 */        printf("connect with server immediately\n");        fcntl(sockfd, F_SETFL, fdopt);        return sockfd;    }    else if(errno != EINPROGRESS)    {        /* 如果连接没有立即建立,那么只有当errno是EINPROGRESS时才表示连接还在进行。否则出错返回 */        printf("unblock connect not support\n");        return -1;    }    fd_set writefds;    struct timeval timeout;    FD_SET(sockfd, &writefds);    timeout.tv_sec = time;    timeout.tv_usec = 0;    ret = select(sockfd+1, NULL, &writefds, NULL, &timeout);    if(ret <= 0)    {        /* select超时或者出错,立即返回 */        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);    /* 调用getsockopt来获取并清除sockfd上的错误 */    if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0)    {        printf("get socket option failed\n");        close(sockfd);        return -1;    }    /* 错误号不为0表示连接出错 */    if(error != 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;}

  但遗憾的是,这种方法存在几处移植性问题。首先,非阻塞的socket可能导致connect始终失败。其次,select对处于EINPROGRESS状态下的socket可能不起作用。最后,对于出错的socket,getsockopt在有些系统(比如Linux上返回-1),而在有些系统(比如源自伯克利的UNIX)上则返回0。这些问题没有一个统一的解决方法。

0 0