select函数
函数原型
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *excetset,const_struct timeval *timeout);
参数说明:
maxfdp1:读、写、异常集合中的文件描述符的最大值加1
readset:读的集合,表示如果有数据可读的套接口,就放到这个集合中
writeset:写的集合,表示如果有数据可写的套接口,就放到这个集合中
excetset:异常的集合,表示如果有数据异常的套接口,就放到这个集合中
timeout:指定一个超时时间,如果是空指针,则不会超时,意味着一定要检测到一个事件才返回
说明:
select函数是利用单进程实现了并发,用于管理多个文件描述符或管理多个I/O,一旦一个或多个I/O检测到感兴趣的事件,select就立刻返回,将返回到的事件填充到对应的集合中,并返回事件个数。此时,就可以轮询这些事件进行挨个处理。这是不会阻塞了,因为select提前阻塞了,select的返回意味着事件的到来。
void FD_ZERO(fd_set *fdset);//清空集合 void FD_SET(int fd,fd_set *fdset);//添加文件描述符到集合中 void FD_CLR(int fd,fd_set *fdset);//将指定文件描述符从集合中移除 void FD_ISSET(int fd,fd_set *fdset);//判断一个文件描述符是否在集合中
使用(认真看注释)
int i; //FD_SETSIZE是select的一个限制,最多能处理的文件描述符的个数 int client[FD_SETSIZE]; int maxi=0;//最大的不空闲的位置 for(int i=0;i<FD_SETSIZE;i++) client[i]=-1;//值为-1表示空闲 int nready;//检测到的事件个数 int maxfd=listenfd; fd_set rset; fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); //感兴趣的是监听套接口 FD_SET(listenfd,&allset); while(1) { /* rset里面是已经改变过的集合,它只保留当前产生的事件的集合,并没有 去监听所有的套接口,所以需要一个变量来临时保存一下 rset=allset; /* 本质思想是:用select统一管理多个套接口是否产生事件(本例中是可读事件, 参数还可以选择还有可写,异常事件) ,在这里,要管理的套接口 有两类,一类是监听套接口,还有一类是已连接套接口 */ nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) { //如果是被信号中断的,继续循环进行监听 if(errno==EINTR) continue; ERR_EXIT("select"); } if(nready==0) continue; //监听套接口发生了可读事件,意味着三次握手已经完成 if(FD_ISSET(listenfd,&rset)) { peerlen=sizeof(peeraddr); //accept不再阻塞了 conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn==-1) ERR_EXIT("accept"); //以前是每个进程中都有一个conn,现在是用单进程的方式实现,单进程只有一个conn,那么就无法 //保存多个客户端的连接信息,所以这时候需要一个数组来保存客户端的信息 for(i=0;i<FD_SETSIZE;i++) { if(client[i]<0) { client[i]=conn; if(i>maxi)//说明最大的不空闲位置发生了改变 maxi=i; break;//说明找到了空闲的位置 } } //如果没有找到空闲的位置 if(i==FD_SETSIZE) { fprintf(stderr,"too many clients\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); //将conn这个套接口加入到集合当中,以便下一次去关心它的可读事件 //此时,这个已连接套接口也是我们所感兴趣的套接口了 FD_SET(conn,&allset); if(conn>maxfd) maxfd=conn; //说明检测到的事件已经处理完了,可以开始下一波的监听(套接口是否产生可读事件)了 if(--nready<=0) continue; } //已连接套接口产生了可读事件 for(int i=0;i<=maxi;i++) { conn=client[i]; if(conn==-1) continue; //说明已连接套接口产生了可读事件,现在就可以读取数据了 if(FD_ISSET(conn,&rset)) { char recvbuf[1024]={0}; int ret=readline(conn,&recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); if(ret==0) { printf("client close\n"); //对方关闭,从集合当中清除 FD_CLR(conn,&allset); //一旦一个套接口关闭,这个位置就空闲了,置为-1,用以容纳更多的客户端 client[i]=-1; } //将收到的这一行输出至标准输出 fputs(recvbuf,stdout); //回射给客户端 writen(conn,recvbuf,strlen(recvbuf)); //所有的事件已被处理,就需要重新监听哪些套接口产生了可读事件 if(--nready<=0) break; } } }
poll函数
函数原型
#include <poll.h>
int poll(struct pollfd* fdarray,usigned long nfds,int timeout);
参数说明
fdarray:第一个参数是指向一个结构数组第一个元素的指针,将我们所关心的一些事件或I/O加入到数组中即可。
nfds:加入数组中的个数。
timeout:超时事件
struct pollfd{ int fd;//文件描述符 short events;//这个文件描述符所感兴趣的事件 short revents;//当事件到来时,到底是哪一个事件发生了返回到revents中 }
使用(认真看注释)
int i; //FD_SETSIZE是select的一个限制,最多能处理的文件描述符的个数 //保存客户端的信息 struct pollfd client[2048]; int maxi=0;//最大的不空闲的位置 for(int i=0;i<2048;i++) client[i].fd=-1;//值为-1表示空闲 int nready;//检测到的事件个数 client[0].fd=listenfd;//第一个添加进去的是监听套接口 client[0].events=POLLIN;//对监听套接口的可读事件感兴趣,而对可写事件并不感兴趣 while(1) { //-1表示永远等待,直到有事件到来的时候才返回 nready=poll(client,maxi+1,-1); if(nready==-1) { //如果是被信号中断的,继续循环进行监听 if(errno==EINTR) continue; ERR_EXIT("select"); } if(nready==0) continue; //如果返回的事件等于POLLIN,监听套接口发生了可读事件 if(client[0].revents & POLLIN) { peerlen=sizeof(peeraddr); //accept不再阻塞了 conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn==-1) ERR_EXIT("accept"); //以前是每个进程中都有一个conn,现在是用单进程的方式实现,单进程只有一个conn,那么就无法 //保存多个客户端的连接信息,所以这时候需要一个数组来保存客户端的信息 for(i=0;i<2048;i++) { if(client[i].fd<0) { client[i].fd=conn; if(i>maxi)//说明最大的不空闲位置发生了改变 maxi=i; break;//说明找到了空闲的位置 } } //如果没有找到空闲的位置 if(i==2048) { fprintf(stderr,"too many clients\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); printf("count=%d\n",++count); //添加事件 client[i].events=POLLIN; //说明检测到的事件已经处理完了,可以开始下一波的监听(套接口是否产生可读事件)了 if(--nready<=0) continue; } //已连接套接口产生了可读事件 for(int i=1;i<=maxi;i++) { conn=client[i].fd; if(conn==-1) continue; //说明已连接套接口产生了可读事件,现在就可以读取数据了 if(client[i].events & POLLIN) { char recvbuf[1024]={0}; int ret=readline(conn,&recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); if(ret==0) { printf("client close\n"); //一旦一个套接口关闭,这个位置就空闲了,置为-1 client[i].fd=-1; close(conn); } //将收到的这一行输出至标准输出 fputs(recvbuf,stdout); //sleep(4); //回射给客户端 writen(conn,recvbuf,strlen(recvbuf)); //所有的事件已被处理,就需要重新监听哪些套接口产生了可读事件 if(--nready<=0) break; } } }