第6章 I/O多路复用
前一章节客户端同时处理两个输入:标准输入和TCP套接字,然而问题在于客户端阻塞于fgets调用期,服务器进程被杀死后,服务器tcp虽然可以正确发送一个fin,但进程正阻塞于标准输入,它无法看到eof,直到从套接字读为止.这样的进程需要一种预先告诉内核的能力,一旦内核发现进程指定的一个或者多个I/O就绪,它就通知进程,这就叫做I/O复用
![]()
select函数
#include <sys/select.h>#include<sys/time.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
timeout参数:告知内核等待所指定的描述符集合的任意一个可以花多少时间
struct timeval{long tv_sec;long tv_usec;};
参数有以下可能:(1) 永远等下去.有一个以上描述符准备好I/O才返回
(2)等待固定一段时间,如果这段时间没I/O准备好.那就直接返回
(3)根本不等待,检查描述符后立刻返回,这称为轮询(polling)
中间三个参数readset,writeset,exceptset指定我们要让内核测试读,写,异常的描述符
异常的一种情况只是某个套接字的带外数据的到达
select使用一个整数数组,其中每个整数的每一位对应一个描述符...其实现隐藏在fd_set跟四个宏
void FD_ZERO(fd_set*fdset);void FD_SET(int fd,fd_set*fdset);void FD_CLR(int fd,fd_set *fdset);int FD_ISSET(int fd,fd_set *fdset);
fd_set rset;FD_ZERO(&rset);FD_SET(1,&rset);FD_SET(4,&rset);FD_SET(5,&rset);
select中间三个参数.如果对某一个条件不感兴趣那就设置为null.nfds的值等于最大描述符值+1,描述符0一直到nfds-1都被测试,该函数返回时,结果将指示哪些描述符就绪,我们使用d_isset来表示该描述符是否就绪,描述符集内任何与未就绪描述符对应的位返回时均会被置为0,因此,每次调用select函数.我们都要把所关心的描述符集的位设置为1,此函数返回时已就绪总数.如果定时器到时了,那么返回0,出错返回-1(比如被中断打断)
描述符就绪条件:
(1)如果套接字接收缓冲区的数据大于等于套接字接收缓冲区低水位标记的大小(就是最低值,可用SO_REVLOWAT改变),这样对套接字进行读操作不会阻塞并且将会返回一个大于0的值(返回准备好读入的数据)
(2)该连接的读半部关闭(接收FIN的TCP).对这样的套接字的读操作不会阻塞并且返回0(返回EOF)
(3)该套接字是监听套接字并且已完成队列不为空,
(4)一个套接字有错误待处理.对这样的套接字的读操作将不会阻塞返回-1.同时errno将设置为错误条件
套接字可写条件
(1)该套接字发送缓冲区的可用空间字节大于等于套接字发送缓冲区低水位的当前大小
(2)该连接写操作关闭,继续写将产生SIGPIPE信号
(3)使用非阻塞的connect套接字已连接.或者失败
(4)其上有一个套接字错误待处理.对这样的套接字的写操作将不阻塞返回-1.
(3)一个套接字存在带外数据,有异常条件待处理
如果键入eof后直接return那么可能在发送的服务器的数据还在路上或者返回给客户端的数据还在路上,标准eof不意味着完成了套接字的读入
当客户端使用eof关闭读时,将使用shutdown半关闭,接着继续select阻塞.直到服务端也发送fin.客户端套接字触发可读.读到0.表示服务端也开始关闭了.
客户端代码:
#include "unp.h"voidstr_cli(FILE *fp, int sockfd){int maxfdp1, stdineof;fd_set rset;char buf[MAXLINE];int n;stdineof = 0;FD_ZERO(&rset);for ( ; ; ) {if (stdineof == 0)FD_SET(fileno(fp), &rset);FD_SET(sockfd, &rset);maxfdp1 = max(fileno(fp), sockfd) + 1;Select(maxfdp1, &rset, NULL, NULL, NULL);- if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {if (stdineof == 1)return; /* normal termination */elseerr_quit("str_cli: server terminated prematurely");}- Write(fileno(stdout), buf, n);
}- if (FD_ISSET(fileno(fp), &rset)) {
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {stdineof = 1;Shutdown(sockfd, SHUT_WR);FD_CLR(fileno(fp), &rset);continue;}Writen(sockfd, buf, n);}}}int main(int argc,char *argv[]){if(argc<3)err_quit("please input IP Address : Port");int connfd=Socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in servaddr;servaddr.sin_family=AF_INET;servaddr.sin_port=htons(atoi(argv[2]));Inet_pton(AF_INET,argv[1],&servaddr.sin_addr);Connect(connfd,(SA*)&servaddr,sizeof(servaddr));str_cli(stdin,connfd);return 0;}
服务端代码:
#include "unp.h"static int create_sock(char *ip){int listenfd;listenfd=Socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in servaddr;servaddr.sin_family=AF_INET;servaddr.sin_port=htons(atoi(ip));Inet_pton(AF_INET,"0.0.0.0",&servaddr.sin_addr);Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));Listen(listenfd,20);return listenfd;}static void deal_cli(int listenfd){int connfd,i;fd_set allset,rset;//allset保存的整个描述符集。但并不会直接select.每次select都会使得清0.很不方便。于是当要select的时候把它付给rsetFD_ZERO(&allset);FD_ZERO(&rset);char buf[1024];//从客户端读取的缓冲区int ret=0;int maxfd=listenfd;//select第一个参数int allfd[MAXLINE];//连接数组for(i=0;i<MAXLINE;++i){allfd[i]=-1;}int nready; //select I/O已经就绪int max=0;//表示已完成连接数组中最大的队列FD_SET(listenfd,&allset);//将监听连接加入allset集合中for(;;){rset=allset;//更新nready=Select(maxfd+1,&rset,NULL,NULL,NULL);if(FD_ISSET(listenfd,&rset)){connfd=Accept(listenfd,NULL,NULL); //从已连接套接字拿取一个出来for(i=0;i<MAXLINE;++i){ //遍历整个已连接数组。找到alifd[i]等于-1(表示该位可用)if(allfd[i]<0){allfd[i]=connfd;break;}}FD_SET(connfd,&allset);if(maxfd<connfd) //每次都需要比较maxfd跟新来的描述符值大小maxfd=connfd;if(max<i)//还要更新最后可连接数组最后的那个已连接索引max=i;if(--nready<=0) //如果该连接已经处理完成。那就continue;}for(i=0;i<max+1;++i){ //遍历以此处理所有已完成连接if(allfd[i]!=-1)//跳过if(FD_ISSET(allfd[i],&rset)){ret=read(allfd[i],buf,1024);if(ret==0){close(allfd[i]);FD_CLR(allfd[i],&allset);allfd[i]=-1;}else if(ret<0){perror("error");}elseWriten(allfd[i],buf,ret);}if(--nready<=0) //如果处理最后一个连接。继续返回到select中去continue;}}}- }
- int main(int argc,char *argv[])
{if(argc<2){err_quit("please input ip aiddress\n");}int listenfd=create_sock(argv[1]);//创建一个套接字deal_cli(listenfd);//开始处理细节return 0;}
poll函数
#include <poll.h>- int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第一个参数是指向一个结构数组第一个元素的指针,每个数组元素都是一个pollfd结构图,用来指定厕所某个给定描述符fd的条件
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */};
要测试的条件由events成员指定,函数在相应的revents成员返回该描述符状态

poll识别三类数据:普通,优先级带和高优先级
相对于TCP和UDP套接字而言,以下条件引起poll返回特定的revent
(1)所有正规的TCP数据和所有UDP数据都被认为是普通数据
(2)TCP的带外数据被认为是优先级带数据
(3)当TCP连接的读半部关闭时,也被认为是普通数据,随后的读操作返回0
(4)TCP连接存在错误可以被认为是错误也可以认为是普通数据,无论哪种情况随后的读操作都将返回-1,并且把error设置为合适的值。可用来接收到RST或者发生超时等条件。
(5) 在监听套接字上有新的连接可用,被视为普通数据,也可以认为是优先级数据,大多数被视为普通数据
(6)非阻塞式connect的完成被认为使得套接字可写
结构数组中元素的个数是由nfds参数所指定
timeout参数指定poll函数返回前等待多长时间,它是一个指定应等待毫秒数的正值
INFTIM 永远等待 0 立即返回 >0 等待指定数据的毫秒数
发生错误时,poll的返回值为-1,若定时器到时之前没有任何描述符就绪,则返回0,否则返回就绪描述符个数,也就是revents成员值为非0的个数
如果我们不再关心某个描述符的属性,那就把对应的pollfd的fd设置为一个负数
/* include fig01 */#include "unp.h"//#include <limits.h> /* for OPEN_MAX */intmain(int argc, char **argv){int i, maxi, listenfd, connfd, sockfd;int nready;ssize_t n;char buf[MAXLINE];socklen_t clilen;long OPEN_MAX = sysconf(_SC_OPEN_MAX);struct pollfd client[OPEN_MAX];struct sockaddr_in cliaddr, servaddr;listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));Listen(listenfd, LISTENQ);client[0].fd = listenfd;client[0].events = POLLRDNORM;for (i = 1; i < OPEN_MAX; i++)client[i].fd = -1; /* -1 indicates available entry */maxi = 0; /* max index into client[] array *//* end fig01 *//* include fig02 */for ( ; ; ) {nready = Poll(client, maxi+1, INFTIM);if (client[0].revents & POLLRDNORM) { /* new client connection */clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);#ifdef NOTDEFprintf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));#endiffor (i = 1; i < OPEN_MAX; i++)if (client[i].fd < 0) {client[i].fd = connfd; /* save descriptor */break;}if (i == OPEN_MAX)err_quit("too many clients");client[i].events = POLLRDNORM;if (i > maxi)maxi = i; /* max index in client[] array */if (--nready <= 0)continue; /* no more readable descriptors */}for (i = 1; i <= maxi; i++) { /* check all clients for data */if ( (sockfd = client[i].fd) < 0)continue;if (client[i].revents & (POLLRDNORM | POLLERR)) {if ( (n = read(sockfd, buf, MAXLINE)) < 0) {if (errno == ECONNRESET) {/*4connection reset by client */#ifdef NOTDEFprintf("client[%d] aborted connection\n", i);#endifClose(sockfd);client[i].fd = -1;} elseerr_sys("read error");} else if (n == 0) {/*4connection closed by client */#ifdef NOTDEFprintf("client[%d] closed connection\n", i);#endifClose(sockfd);client[i].fd = -1;} elseWriten(sockfd, buf, n);if (--nready <= 0)break; /* no more readable descriptors */}}}}/* end fig02 */
浙公网安备 33010602011771号