Linux 高性能服务器编程——I/O复用的高级应用

时间:2021-02-15 17:57:16



高级应用一:非阻塞connect
connect系统调用的man手册中有如下的一段内容:
       EINPROGRESS              The  socket  is non-blocking 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这种错误发生在对非阻塞的connect,而连接又没有建立时根据 man 文档解释,在这种情况下我们可以调用 select 、 poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用 getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功,否则连接失败。
#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 readfds;    fd_set writefds;    struct timeval timeout;    FD_ZERO( &readfds );    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;}

非阻塞connect的细节:
  • 尽管套接字是非阻塞的,如果连接到的服务器在同一个主机上,那么当我们调用connect时,连接通常立即建立,我们必须处理这种情况。
  • 源自Berkeley的实现(和POSIX)有关于select和非阻塞connect的以下两个规则:(1)当连接成功建立时,描述符变为可写。 (2)当连接建立遇到错误时,描述符变为既可读又可写。

       对于阻塞的socket,如果其上的connect调用在TCP三次握手完成前被中断(譬如说捕获了某个信号),将会发生什么呢?        假设被中断的connect调用不由内核自动重启,那么它将返回EINTR ,我们不能再次调用connect 等待未完成的连接继续完成。这样做将导致返回EADDRINUSE 错误。这种情况下我们只能调用select,连接建立成功时select返回socket可写条件,连接建立失败时,select返回socket可读又可写条件。




高级应用二:同时处理TCP和UDP服务
       在此之前,我们讨论的服务器程序都只监听一个端口。在实际应用中,有不少服务器程序能同时监听多个端口,比如超组服务xinet。
       从bind系统调用的参数看,一个socket只能绑定一个socket地址,即一个socket只能用来监听一个端口。因此,服务器如果要监听多个端口就必须创建多个socket,并将它们分别绑定到各个端口上。这样一来,服务器程序就需要同时管理多个监听socket,I/O复用技术就有了用武之地。
       另外,即使是同一个端口,如果服务器要同时处理该端口上TCP和UPD请求,也是需要创建两个不同的socket,并将它们都绑到该端口上。
#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <assert.h>#include <stdio.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <fcntl.h>#include <stdlib.h>#include <sys/epoll.h>#include <pthread.h>#define MAX_EVENT_NUMBER 1024#define TCP_BUFFER_SIZE 512#define UDP_BUFFER_SIZE 1024int 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;}void addfd( int epollfd, int fd ){    epoll_event event;    event.data.fd = fd;    //event.events = EPOLLIN | EPOLLET;    event.events = EPOLLIN;    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );    setnonblocking( fd );}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 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 listenfd = socket( PF_INET, SOCK_STREAM, 0 );    assert( listenfd >= 0 );    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );    assert( ret != -1 );    ret = listen( listenfd, 5 );    assert( ret != -1 );    bzero( &address, sizeof( address ) );    address.sin_family = AF_INET;    inet_pton( AF_INET, ip, &address.sin_addr );    address.sin_port = htons( port );    int udpfd = socket( PF_INET, SOCK_DGRAM, 0 );    assert( udpfd >= 0 );    ret = bind( udpfd, ( struct sockaddr* )&address, sizeof( address ) );    assert( ret != -1 );    epoll_event events[ MAX_EVENT_NUMBER ];    int epollfd = epoll_create( 5 );    assert( epollfd != -1 );    addfd( epollfd, listenfd );    addfd( epollfd, udpfd );    while( 1 )    {        int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );        if ( number < 0 )        {            printf( "epoll failure\n" );            break;        }            for ( int i = 0; i < number; i++ )        {            int sockfd = events[i].data.fd;            if ( sockfd == listenfd )            {                struct sockaddr_in client_address;                socklen_t client_addrlength = sizeof( client_address );                int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );                addfd( epollfd, connfd );            }            else if ( sockfd == udpfd )            {                char buf[ UDP_BUFFER_SIZE ];                memset( buf, '\0', UDP_BUFFER_SIZE );                struct sockaddr_in client_address;                socklen_t client_addrlength = sizeof( client_address );                ret = recvfrom( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, &client_addrlength );                if( ret > 0 )                {                    sendto( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, client_addrlength );                }            }            else if ( events[i].events & EPOLLIN )            {                char buf[ TCP_BUFFER_SIZE ];                while( 1 )                {                    memset( buf, '\0', TCP_BUFFER_SIZE );                    ret = recv( sockfd, buf, TCP_BUFFER_SIZE-1, 0 );                    if( ret < 0 )                    {                        if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )                        {                            break;                        }                        close( sockfd );                        break;                    }                    else if( ret == 0 )                    {                        close( sockfd );                    }                    else                    {                        send( sockfd, buf, ret, 0 );                    }                }            }            else            {                printf( "something else happened \n" );            }        }    }    close( listenfd );    return 0;}