select的限制
1.一个进程能打开的最大文件描述符是有限的
2.FD_SETSIZE(fd_set)限制,select内部使用一个数据结构fd_set,它的容量最大不能超过FD_SETSIZE。
poll的限制
一个进程能打开的最大文件描述符是有限的
上面的进程能打开的最大文件描述符的个数可通过命令ulimit -n number更改,但也不是无限大,还受到系统所能打开的最大文件描述符个数的限制,这个大小和内存有关
select和poll的另一个主要的缺点是:
内核要遍历所有我们所感兴趣的文件描述符,直到找到发生事件的文件描述符,这样的话,当并发数增长的时候,内核要遍历的文件描述符的个数也随之增大,导致效率下降。这也就是epoll引入的原因。
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
epoll
epoll_create函数
int epoll_create(intsize);
作用:
该函数生成一个epoll专用的文件描述符epoll_ctl函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
作用:
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符,加入epoll_ctl进行管理
event:事件,对文件描述符所感兴趣的事件,指向epoll_event的指针;
返回值
如果调用成功返回0,不成功返回-1
说明
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; //感兴趣的事件是可读还是可写 epoll_data_t data; /* User data variable */这是epoll高效的地方,数据类型是上面的那个共用体 };
events可以是以下几个宏的集合:
EPOLLIN:触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);EPOLLOUT:触发该事件,表示对应的文件描述符上可以写数据;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
epoll_wait函数
int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
作用:
该函数用于轮询I/O事件的发生
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可返回发生事件数。
返回值:
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目
代码
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/epoll.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <vector> #include <algorithm> typedef std::vector<struct epoll_event> EventList; #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) void activate_nonblock(int fd) { int ret; int flags=fcntl(fd,F_GETFL); if(flags==-1) ERR_EXIT("fcntl"); ret=fcntl(fd,F_SETFL,flags); if(ret==-1) ERR_EXIT("fcntl"); } ssize_t readn(int fd,void *buf,size_t count) { size_t nleft=count; ssize_t nread; char *bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; return -1; } else if(nread==0) return count-nleft; bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd,void *buf,size_t count) { size_t nleft=count; ssize_t nwritten; char *bufp=(char*)buf; while(nleft>0) { if((nwritten=write(fd,bufp,nleft))<0) { if(errno==EINTR) continue; return -1; } else if(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } return count; } ssize_t recv_peek(int sockfd,void *buf,size_t len) { while(1) { int ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1 && errno==EINTR) continue; return ret; } } ssize_t readline(int sockfd,void *buf,size_t maxline) { int ret; int nread; char *bufp=(char *)buf; int nleft=maxline; while(1) { ret=recv_peek(sockfd,bufp,nleft); if(ret<0) return ret; else if(ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufp[i]=='\n') { ret=readn(sockfd,bufp,i+1); if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread; } return -1; } void handle_sigchld(int sig) { while(waitpid(-1,NULL,WNOHANG)>0); } void handle_sigpipe(int sig) { printf("recv a sig=%d\n",sig); } int main(void) { int count=0; //signal(SIGPIPE,SIG_IGN); signal(SIGPIPE,handle_sigpipe);//演示确实收到了一个信号handle_sigpipe /* signal(SIGCHLD,SIG_IGN);*/ signal(SIGCHLD,handle_sigchld); int listenfd; if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) /*if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) */ ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/ /*inet_aton("127.0.0.1,&servaddr.sin_addr");*/ int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)//设置一个选项地址重复利用 ERR_EXIT("setsockopt"); if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//绑定 ERR_EXIT("bind"); if(listen(listenfd,SOMAXCONN)<0)//监听 ERR_EXIT("listen"); //以下就是epoll独有的,上面的都是copy poll而来的 //clients保存的是客户端的套接字 std::vector<int> clients; int epollfd; epollfd=epoll_create1(EPOLL_CLOEXEC);//创建一个epoll实例 //通过man epoll 可了解下面各个步骤 struct epoll_event event; //感兴趣的一个文件描述符 event.data.fd=listenfd; //感兴趣的事件是EPOLLIN,以及以边沿的方式触发 event.events=EPOLLIN | EPOLLET; //将感兴趣的加入epoll进行管理(将监听套接口以及感兴趣的事件加入) epoll_ctlz(epollfd,EPOLL_CTL_ADD,listenfd,&event); EventList events(16); struct sockaddr_in peeraddr; socklen_t peerlen; int conn; int i; int nready; while(1) { //检测所返回的事件,哪些I/O产生了事件 //返回的事件保存在events当中 nready=epoll_wait(epollfd,&*events.begin(),static_cast<int>(events.size()),-1); if(nready==-1) { if(errno==EINTR) continue; ERR_EXIT("epoll_wait"); } if(nready==0) continue; //容量不够了,扩容 if((size_t)nready==events.size()) events.resize(events.size()*2); for(int i=0;i<nready;i++) { if(events[i].data.fd==listenfd) { peerlen=sizeof(peeraddr); conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn==-1) ERR_EXIT("accept"); printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); printf("count=%d\n",++count); //把已连接套接字保存起来 clients.push_back(conn); //并且设置为非阻塞模式 activate_nonblock(conn); //这个新获得的套接字下一次也要监听它的可读事件 event.data.fd=conn; //感兴趣的是可读事件并且以边沿的方式触发 event.events=EPOLLIN | EPOLLET; //加入epoll进行管理 epoll_ctl(epollfd,EPOLL_CTL_ADD,conn,&event); } //下面就是已连接套接字产生的可读事件 else if(events[i].events & EPOLLIN) { conn=events[i].data.fd; if(conn<0) continue; char recvbuf[1024]={0}; int ret=readline(conn,recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); if(ret==0) { printf("client closr\n"); close(conn); event=events[i]; //从epollfd中删除 epoll_ctl(epollfd,EPOLL_CTL_DEL,conn,&event); //clients保存的是已连接的套接字,所以删除掉这个断开的 clients.erase(std::remove(clients.begin(),clients.end(),conn),clients.end()); } fputs(recvbuf,stdout); writen(conn,recvbuf,strlen(recvbuf)); } } } return 0; }