1 select的低效率
select/poll函数效率比较低,主要有以下两个原因:
(1)调用select函数后需要对所有文件描述符进行循环查找
(2)每次调用select函数时都需要向该函数传递监视对象信息
在这两个原因中,第二个原因是主要原因:每次调用select函数时,应用程序都要将所有文件描述符传递给操作系统,这给程序带来很大的负担。在高并发的环境下,无论怎样优化应用程序的代码,都无法完成应用的服务。
所以,select与poll并不适合以Web服务器端开发为主流的现代开发环境,只在要求满足以下两个条件是适用:
(1)服务器端接入者少
(2)程序要求兼容性
2 Linux的epoll机制
由上一节,我们需要一种类似于select的机制来完成高并发的服务器。需要有以下两个特点:
(1)应用程序仅向操作系统传递1次监视对象
(2)监视范围或内容发生变化是,操作系统只通知发生变化的事项给应用程序
幸运的是,的确存在这样的机制。Linux的支持方式是epoll,Windows的支持方式是IOCP。
3 epoll函数原型
epoll操作由三个函数组成:
#include <sys/epoll.h> int epoll_create(int size); //成功时返回epoll文件描述符,失败时返回-1 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //成功时返回0,失败时返回-1 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); //成功时返回发生事件的文件描述数,失败时返回-1
(1)epoll_create:创建保存epoll文件描述符的空间。
调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”。但要注意:size参数只是应用程序向操作系统提的建议,操作系统并不一定会生成一个大小为size的epoll例程。
(2)epoll_ctl:向空间注册并注销文件描述符。
参数epfd指定注册监视对象的epoll例程的文件描述符,op指定监视对象的添加、删除或更改等操作,有以下两种常量:
1)EPOLL_CTL_ADD:将文件描述符注册到epoll例程
2)EPOLL_CTL_DEL:从epoll例程中删除文件描述符
3)EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况
fd指定需要注册的监视对象文件描述符,event指定监视对象的事件类型。epoll_event结构体如下:
struct epoll_event { __uint32_t events; epoll_data_t data; } typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; }epoll_data_t;
epoll_event的成员events中可以保存的常量及所指的事件类型有以下:
1)EPOLLIN:需要读取数据的情况
2) EPOLLOUT:输出缓冲为空,可以立即发送数据的情况
3) EPOLLPRI:收到OOBO数据的情况
4) EPOLLRDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用
5) EPOLLERR:发生错误的情况
6) EPOLLET:以边缘触发的方式得到事件通知
7) EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数EPOLL_CTL_MOD,再次设置事件。
(3)epoll_wait:与select函数类似,等待文件描述符发生变化。操作系统返回epoll_event类型的结构体通知监视对象的变化。timeout函数是为毫秒为单位的等待时间,传递-1时,一直等待直到事件发生。声明足够大的epoll_event结构体数组后,传递给epoll_wait函数时,发生变化的文件符信息将被填入该数组。因此,不需要像select函数那样针对所有文件符进行循环。
4 基于epoll的echo服务器代码:
#define BUF_SIZE 1024 #define EPOLL_SIZE 50 void error_handling(char *buf); int main(int argc, char *argv[]) { int listenfd, connfd; struct sockaddr_in serv_addr; socklen_t socklen; char buf[BUF_SIZE]; int epfd, event_cnt; struct epoll_event *ep_events; struct epoll_event event; if (argc != 2) { printf("Usage: echo <port>\n"); exit(1); } listenfd = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_addr, 0, sizeof(serv_addr); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (bind(listenfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error\n"); if (listen(serv_addr, 5) == -1) error_handling("listen() error\n"); epfd = epoll_create(EPOLL_SIZE); ep_events = malloc(sizeof(epoll_event)*EPOLL_SIZE); event.event = EPOLLIN; event.data.fd = listenfd; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event); for (;;) { event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if (event_cnt == -1) error_handling("epoll_wait() error\n"); for (int i = 0; i < event_cnt; ++i) { if (ep_events[i].data.fd == listenfd) { connfd = accept(listenfd, NULL, NULL); event.events = EPOLLIN; event.data.fd = connfd; epoll_ctl(pefd, EPOLL_CTL_ADD, connfd, &event); printf("connect another client\n"); } else { int nread = read(ep_events[i].dada.fd, buf, BUF_SIZE); if (nread == 0) { close(ep_events.data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events.data.fd, NULL); printf("disconnect with a client\n"); } else { write(ep_events[i].data.fd, buf, nread); } } } } close(listenfd); close(epfd); return 0; } void error_handling(char* buf) { printf("%s\n", buf); exit(1); }