Linux Socket 编程中I/O Multiplexing 主要通过三个函数来实现:select, poll,epoll来实现。I/O Multiplexing,先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已准备好可以进行I/O。本文具体介绍一下select 和poll的用法,给出简单的demo代码,简要分析一下这两个函数的使用易出错的地方。
#include<sys/select.h> int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval* restrict tvptr); //Returns: count of ready descriptors, 0 on timeout, -1 on error
中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或出于异常条件的各个描述符,设置为NULL则表示不关心。每个描述符集存放在一个fd_set数据类型中。这种数据类型为每一可能的描述符保持一位。描述符集的函数接口(可能实现为宏)包括:调用FD_ZERO将一个指定的fd_set变量的所有位设置为0;调用FD_SET设置一个fd_set变量的指定位;调用FD_CLR将一指定位清楚;调用FD_ISSET测试一指定位是否设置。
#include <sys/select.h> int FD_ISSET(int fd, fd_set *fdset); //Returns: nonzero if fd is in set, 0 otherwise void FD_CLR(int fd, fd_set *fdset); void FD_SET(int fd, fd_set *fdset); void FD_ZERO(fd_set *fdset);
文件描述符集fdset中的文件描述符的个数是有限制的,最大值由FD_SETSIZE指定,一般为1024.
Select 最后一个参数用于设置超时值,当select监听达到超时值时还未有关心的事件发生则返回,函数返回值为0.
struct timeval{ long tv_sec;//second long tv_usec;//minisecond }
超时参数如果设置为 NULL 则无限等待。
下面来是一个简单的select Echo server:
// simpleEcho.cpp
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <vector> #include <string.h> #include <stdlib.h> #include <fcntl.h> #define SEVER_PORT 1314 #define MAX_LINE_LEN 1024 using namespace std; int main() { struct sockaddr_in cli_addr, server_addr; socklen_t sock_len; vector<int> client(FD_SETSIZE,-1); fd_set rset,allset; int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,one; char addr_str[INET_ADDRSTRLEN],buf[MAX_LINE_LEN]; bzero(&server_addr,sizeof server_addr); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SEVER_PORT); listenfd = socket(AF_INET,SOCK_STREAM,0); one = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one)); if(bind (listenfd ,(struct sockaddr *)&server_addr ,sizeof server_addr) < 0 ) { printf("socket bind error" ); return 0; } listen(listenfd ,10); FD_ZERO(&allset); FD_SET(listenfd ,&allset ); maxfd = listenfd ; maxid = -1 ; while(1 ) { rset = allset; //! nready = select (maxfd + 1, &rset,NULL,NULL,NULL); if(nready < 0 ) { printf("select error! \n" ); exit(1 ); } if(FD_ISSET (listenfd , &rset )) { sock_len = sizeof cli_addr; connfd = accept (listenfd ,(struct sockaddr *)&cli_addr , &sock_len); printf("recieve from : %s at port %d\n" , inet_ntop(AF_INET,&cli_addr .sin_addr ,addr_str ,INET_ADDRSTRLEN ),cli_addr .sin_port ); for(ix = 0 ; ix < static_cast< int>(client .size()); ix++) { if(client[ix] < 0 ) { client[ix] = connfd ; break; } } printf("client[%d] = %d\n" ,ix ,connfd ); if( FD_SETSIZE == ix) { printf("too many client! \n" ); exit(1 ); } if( connfd > maxfd) { maxfd = connfd; } FD_SET(connfd, &allset ); if(ix > maxid ) { maxid = ix; } if(--nready == 0) { continue; } } for(ix = 0 ; ix <= maxid; ix++) //<= { if((sockfd = client [ix ]) < 0) { continue; } if(FD_ISSET (sockfd ,&rset )) { if( 0 == (nrcv = read(sockfd,buf,MAX_LINE_LEN ))) { close(sockfd); client[ix] = -1 ; FD_CLR(sockfd ,&allset ); } else { printf("RECIEVE: %s \n" ,buf ); write(sockfd,buf,nrcv); } } if(--nready == 0) { break; } } } return 0; }
在使用select 的时候要注意两点:
第一个参数需要是当前所关心的文件描述符中最大的一个+1