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