高级应用一:非阻塞connect
connect系统调用的man手册中有如下的一段内容:
[cpp] view plain copy print
- EINPROGRESS
- 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
- for the failure).
这段话描述了connect出错时的一种errno值: EINPROGRESS
。 这种错误发生在对非阻塞的connect,而连接又没有建立时
。
根据 man 文档解释,在这种情况下我们可以调用 select 、 poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用 getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功,否则连接失败。
[cpp] view plain copy print
- #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 1023
- int 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;
- 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 );
- struct sockaddr* )&address, sizeof( address ) );
- if ( ret == 0 )
- {
- /*如果连接成功,则恢复sockfd的属性,并立即返回之*/
- "connect with server immediately\n" );
- fcntl( sockfd, F_SETFL, fdopt );
- return sockfd;
- }
- else if ( errno != EINPROGRESS )
- {
- /*如果连接没有立即建立,那么只有当errno是EINPROGRESS时才表示连接还在进行,否则出错返回*/
- "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超时或者出错,立即返回*/
- "connection time out\n" );
- close( sockfd );
- return -1;
- }
- if ( ! FD_ISSET( sockfd, &writefds ) )
- {
- "no events on sockfd found\n" );
- close( sockfd );
- return -1;
- }
- int error = 0;
- sizeof( error );
- /*调用getsockopt来获取并清除sockfd上的错误*/
- if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 )
- {
- "get socket option failed\n" );
- close( sockfd );
- return -1;
- }
- /*错误码不为0表示连接出错*/
- if( error != 0 )
- {
- "connection failed after select with the error: %d \n", error );
- close( sockfd );
- return -1;
- }
- /*连接成功*/
- "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 )
- {
- "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,并将它们都绑到该端口上。
[cpp] view plain copy prin
- #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 1024
- int 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 )
- {
- "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;
- 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 );
- struct sockaddr* )&address, sizeof( address ) );
- assert( ret != -1 );
- ret = listen( listenfd, 5 );
- assert( ret != -1 );
- 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 );
- 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 )
- {
- "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;
- 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 ];
- '\0', UDP_BUFFER_SIZE );
- struct sockaddr_in client_address;
- sizeof( client_address );
- struct sockaddr* )&client_address, &client_addrlength );
- if( ret > 0 )
- {
- struct sockaddr* )&client_address, client_addrlength );
- }
- }
- else if ( events[i].events & EPOLLIN )
- {
- char buf[ TCP_BUFFER_SIZE ];
- while( 1 )
- {
- '\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
- {
- "something else happened \n" );
- }
- }
- }
- close( listenfd );
- return 0;
- }