结论: epoll 要优于 select , 编程模型基本一致; 请注意,不论是epoll 还是 select 都不是具有并发能力的服务器,仅仅是io复用题外话: 在io复用中把监听套接字设为非阻塞觉得理论麻烦的,可以直接往下拉,有代码例子;select 的缺陷:1.在sys/select.h 中 __FD_SETSIZE 为1024 , 意味默认情况最多监控1024个描述符,当然可以修改这个文件重新编译2.select 的实现 ,在fs/select.c 中:int do_select(int n, fd_set_bits fds, s64 timeout) { //略 for (i = 0; i < n; ++rinp, ++routp, ++rexp) { //略 } } 在实现中 , 每次将经过 n-1 次循环, 随着描述符越多,性能线性下降3.select 的3个 fd_set(read,write,except) 都是值-结果: 例如:fd_set rset,allset;while(1){ rset = allset; //每次需要重置 select(maxfd+1, rset, ….)}这种每次重置意味不断的从用户空间往内核空间中复制数据,操作系统一旦轮询完成后,再将数据置位,然后复制到用户空间,就是这种不断的复制造成了性能下降,还不得不这么干;epoll 解决了select缺陷;epoll 给每个需要监听的描述符都设置了一个或多个 event : struct epoll_event event; event.events = EPOLLIN; //读操作 ,如要监控多个可以 EPOLLIN|EPOLLOUT; event.data.fd = listensock; //赋值要监听的描述符, 就像 FD_SET(listensock,&rset);epoll的描述符限制可以查看 cat /proc/sys/fs/epoll/max_user_watches复制数据也仅仅在 epoll_ctl (…,EPOLL_CTL_ADD,…) 添加时(EPOLL_CTL_ADD),复制一次到epoll_create 所创建的红黑树中3. 最重要的是,epoll_wait 并不会像select 去轮询, 而是在内部给监听的描述符一个callback.一旦对应的事件发生(例如:EPOLLIN) 就将此事件添加到一个链表中.epoll_wait(此函数类似select) 的功能就是去链表中收集发生事件相应的 struct epoll_event;总的来说:epoll_create 在操作系统中创建一个用于存放struct epoll_event 的空间epoll_ctl 在空间内 添加,修改,删除 struct epoll_event (内含描述符);epoll_wait 收集已经发生事件的描述符;close 关闭(减少引用) epoll一个类似 select 的echo 服务器修改版本:echo.c#define EPOLL_SIZE 100 int listensock = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in serv_addr, cli_addr; socklen_t socklen = sizeof(serv_addr); memset(&serv_addr,0,socklen); memset(&cli_addr,0,socklen); serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(PORT); serv_addr.sin_family = AF_INET; if(bind(listensock,(SA)&serv_addr,sizeof(serv_addr)) < 0){ perror(“bind”); return 0; } if(listen(listensock,BACKLOG) < 0){ perror(“listen”); return 0; } //向操作系统请求 创建一个 epoll , 参数看man int epfd = epoll_create(EPOLL_SIZE); if(epfd < 0){ perror(“epoll_create”); return 0; } //监听listensock ,类似 FD_SET(listensock, &rset) struct epoll_event event; event.events = EPOLLIN; //读取 . man中有详细解释 event.data.fd = listensock; if(epoll_ctl(epfd,EPOLL_CTL_ADD,listensock,&event) < 0) //把listensock 注册到epfd,用于监听event事件 { perror(“epoll ctl”); return 0; } //分配一块内存. 在 epoll_wait 返回后将存放发生事件的结构体 struct epoll_event * ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE); if(ep_events == NULL){ perror(“malloc”); return 0; } puts(“server is running”); int n = 0; int client_fd = 0; char buf[BUFSIZ]; int len = 0,return_bytes = 0, tmp_fd = 0; while(1){ //收集已经发生事件的描述符 , 返回值与select一致 n = epoll_wait(epfd,ep_events,EPOLL_SIZE,-1); if( n < 0){ perror(“epoll_wait”); break; } printf("%d events returned\n", n); for ( int i = 0; i < n ; ++i){ //如果有人连接 if( listensock == ep_events[i].data.fd){ socklen = sizeof(cli_addr); client_fd = accept(listensock,(SA*)&cli_addr,&socklen); //如果有人连接,则加入epoll event.events = EPOLLIN; //读取 event.data.fd = client_fd; if(epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&event) < 0){ perror(“epoll add failed”); continue; } printf(“accepted,client_fd:%d,ip:%s,port:%d\n”, client_fd,inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port)); } else { tmp_fd =ep_events[i].data.fd; readagain: len = read(ep_events[i].data.fd,buf,BUFSIZ); //如果出错 if(len < 0) { if(errno == EINTR) goto readagain; else{ return_bytes = snprintf(buf,BUFSIZ-1, “clientfd:%d errorno:%d\n”,tmp_fd,errno); buf[return_bytes] = 0; epoll_ctl(epfd,EPOLL_CTL_DEL,tmp_fd,NULL); //从epoll中移除 close(tmp_fd); //close(socket) perror(buf); } } else if( 0 == len){ // 对端断开 return_bytes = snprintf(buf,BUFSIZ-1, “clientfd:%d closed\n”,tmp_fd); buf[return_bytes] = 0; epoll_ctl(epfd,EPOLL_CTL_DEL,tmp_fd,NULL); close(tmp_fd); puts(buf); } else{ //echo write(tmp_fd,buf,len); } } } }条件触发: epoll 的默认行为就是条件触发(select也是条件触发); 通过修改代码得结论 先添加一个BUFF_SIZE#define BUFF_SIZE 4把read(ep_events[i].data.fd,buf,BUFSIZ) 的BUFSIZ 修改成BUFF_SIZE然后 telnet 此服务器 : telnet 127.0.0.1 9988 , 随意写一些数据给服务器先看结果,这是我这里的输出:fuck@ubuntu:/sockettest$ ./epoll_serv server is running1 events returnedaccepted,client_fd:5,ip:127.0.0.1,port:544041 events returned1 events returned1 events returned1 events returned1 events returned1 events returned让read 最多只能读4个字节的唯一原因是 ,证明什么是条件触发通过结果得到结论: 只要此缓冲区内还有数据, epoll_wait 将不断的返回, 这个就是默认情况下epoll的条件触发;边缘触发:这个需要先做个实验才能理解,纯文字估计不太好理解;唯一需要修改的代码是在accept下面的一行:event.events = EPOLLIN | EPOLLET; // EPOLLET 就是边缘触发注意 read 那行代码,最多接受的字节最好在 14 之间 : read(…,…, 4);否则效果不明显接着telnet ,尝试一下每次写给服务器超过 5个字节我这里就不贴服务器输出了 , 可以看到这时 epoll_wait 无论怎么样只会返回一次了 ;先给结论: 只有当客户端写入(write,send,…) , epoll_wait 才会返回且只返回一次;注意与条件触发的不同: 条件触发情况下只要接受缓冲区有数据即返回, 边缘触发不会;由于这种只返回一次的特性 , EPOLLET 一般情况下都将采用非阻塞 O_NONBLOCK 的方式来读取;