[置顶] epoll服务器的介绍和实现
2017-07-03 13:05
204 查看
一、epoll解释:
epoll是Linux下多路复用IO接口select/poll的增强版本,Linux内核为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create、epoll_ctl、epoll_wait。它是在2.5.44内核中被引进的,在2.6内核中得到广泛应用。
它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合。
获取事件的时候,它无须遍历整个被侦听的文件描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll除了提供select/poll 那种IO事件的电平触发外,还提供了边沿触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait的调用,提高应用程序效率。
1.epoll底层实现
epoll在底层实现了自己的高速缓存区,并且建立了一个红黑树用于存放socket,另外维护了一个链表用来存放准备就绪的事件。
2.epoll工作原理:
执行epoll_ create时,创建了红黑树和就绪链表,执行epoll_ ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
3.epoll服务器代码
4.epoll的优缺点:
优点:
a. 支持一个进程打开大数目的socket描述符(FD)
b.IO效率不随FD数目增加而线性下降
c.使用mmap加速内核与用户空间的消息传递。
5.总结
(1)select,poll实现需要自己不断轮询所有fd集合直到设备就绪,期间可能要睡眠和唤醒多次交替。epoll也需要调用 epoll_ wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但它会在设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。
虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。
epoll是Linux下多路复用IO接口select/poll的增强版本,Linux内核为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create、epoll_ctl、epoll_wait。它是在2.5.44内核中被引进的,在2.6内核中得到广泛应用。
它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合。
获取事件的时候,它无须遍历整个被侦听的文件描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll除了提供select/poll 那种IO事件的电平触发外,还提供了边沿触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait的调用,提高应用程序效率。
1.epoll底层实现
epoll在底层实现了自己的高速缓存区,并且建立了一个红黑树用于存放socket,另外维护了一个链表用来存放准备就绪的事件。
2.epoll工作原理:
执行epoll_ create时,创建了红黑树和就绪链表,执行epoll_ ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
3.epoll服务器代码
#include<stdio.h> #include<sys/socket.h> #include<sys/types.h> #include<netinet/in.h> #include<arpa/inet.h> #include<fcntl.h> #include<stdlib.h> #include<unistd.h> #include<sys/epoll.h> #include<string.h> typedef struct ep_buff { int fd; char buff[1024]; }ep_buff_t,*ep_buff_p; void *alloc_ep_buff(int fd) { ep_buff_p n=(ep_buff_p)malloc(sizeof(ep_buff_t)); if(!n) { perror("malloc"); exit(6); } n->fd=fd; return n; } int set_fd_nonblack() {} int myread() { while(1) {} } int mywrite() { while(1) {} } int myaccept(int epfd,int listen_sock) {} static void usage(const char*proc) { printf("Usage: %s [local_ip] [local_port]\n",proc); } int startup(const char*_ip,int _port) { int sock=socket( AF_INET ,SOCK_STREAM, 0); if(sock<0) { perror("socket"); exit(2); } int opt=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); exit(3); } if(listen(sock,10)) { perror("listen"); exit(4); } return sock; } int main(int argc,char* argv[]) { if(argc!=3) { usage(argv[0]); return 1; } //create epoll model int listen_sock=startup(argv[1],atoi(argv[2])); int epfd=epoll_create(256); if(epfd<0) { perror("epfd"); return 5; } struct epoll_event ev; ev.events=EPOLLIN | EPOLLET; ev.data.ptr=alloc_ep_buff(listen_sock); epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev); int nums=-1; struct epoll_event revs[64]; int timeout =-1; while(1) { switch((nums=epoll_wait(epfd,revs,64,timeout))) { case 0: printf("timeout...\n"); break; case -1: perror("epoll_wait"); break; default: { int i=0; for(;i<nums;i++) { int sock=((ep_buff_p)(revs[i].data.ptr))->fd; if(sock==listen_sock && (revs[i].events&EPOLLIN)) { struct sockaddr_in client; socklen_t len=sizeof(client); int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len); if(new_sock<0) { perror("accept"); continue; } printf("get client:[%s:%d]",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); ev.events=EPOLLIN; ev.data.ptr=alloc_ep_buff(new_sock); epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev); }//listen_sock else if(sock!=listen_sock) { if(revs[i].events&EPOLLIN)//normol fd read evevts readlly { char buf[10240]; ssize_t s=read(sock,buf,sizeof(buf)-1); if(s>0) { printf("client:%s\n",buf); ev.events=EPOLLOUT; // ((ep_buff_p)(ev.data.ptr))->fd=sock; epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev); } else if(s==0) { printf("client quit!!\n"); close(sock); epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL); free(revs[i].data.ptr); } else { perror("read"); close(sock); epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL); free(revs[i].data.ptr); } }//litetn_sock else if(revs[i].events&EPOLLOUT)//write events readlly { const char* msg="HTTP/1.0 ok 200\r\n\r\n<html><h1>hello epoll!</h1></html>"; write(sock,msg,strlen(msg)); close(sock); epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL); free(revs[i].data.ptr); } else {} } } } break; } } return 0; }
4.epoll的优缺点:
优点:
a. 支持一个进程打开大数目的socket描述符(FD)
b.IO效率不随FD数目增加而线性下降
c.使用mmap加速内核与用户空间的消息传递。
5.总结
(1)select,poll实现需要自己不断轮询所有fd集合直到设备就绪,期间可能要睡眠和唤醒多次交替。epoll也需要调用 epoll_ wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但它会在设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。
虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。
相关文章推荐
- Linux 系统批量管理工具介绍,如何实现对一万台服务器的同时批量管理?
- DNS服务器概念的简单的介绍,与搭建一个简单的DNS名称缓存服务器,实现域名解析(一)
- 几种并发服务器模型的实现:多线程,多进程,select,poll,epoll
- Linux非阻塞IO(八)使用epoll重新实现非阻塞的回射服务器
- epoll实现服务器高并发
- 用epoll实现异步的Echo服务器
- Linux + C + Epoll实现高并发服务器(线程池 + 数据库连接池)
- HTTP协议的介绍,Web服务器配置,虚拟主机的配置,如何用SSL实现HTTPS。
- Linux 下使用epoll实现转发服务器的小DEMO
- Linux网络编程——tcp并发服务器(epoll实现)
- 用epoll实现异步的Echo服务器
- linux epoll的介绍,操作和具体实现
- 分享:基于epoll实现的一个简单的web服务器
- epoll去实现一个服务器
- 关于epoll服务器反馈的简单实现
- 全面介绍网站性能优化之数据库及服务器架构实现负载均衡等实用知识
- 基于epoll实现的一个简单web服务器
- [置顶] 在Ubuntu下实现一个简单的Web服务器
- Epoll实现服务器高并发
- Epoll模型服务器实现