本文原创版权归 csdn piaojun_pj 全部,转载请详细标明原创做者及出处,以示尊重!!linux
做者:piaojun_pj编程
原文:http://blog.csdn.net/piaojun_pj/article/details/6103709服务器
epoll的优势:
1.支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有必定限制的,由FD_SETSIZE设置,默认值是2048。对于那些须要支持的上万链接数目的IM服务器来讲显然太少了。这时候你一是能够选择修改这个宏而后从新编译内核,不过资料也同时指出这样会带来网络效率的降低,二是能够选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面建立进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,因此也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。网络
2.IO效率不随FD数目增长而线性降低
传统的select/poll另外一个致命弱点就是当你拥有一个很大的socket集合,不过因为网络延时,任一时间只有部分的socket是"活跃"的,可是select/poll每次调用都会线性扫描所有的集合,致使效率呈现线性降低。可是epoll不存在这个问题,它只会对"活跃"的socket进行操做---这是由于在内核实现中epoll是根据每一个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其余idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,由于这时候推进力在os内核。在一些 benchmark中,若是全部的socket基本上都是活跃的---好比一个高速LAN环境,epoll并不比select/poll有什么效率,相反,若是过多使用epoll_ctl,效率相比还有稍微的降低。可是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。架构
3.使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。不管是select,poll仍是epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存拷贝就很重要,在这点上,epoll是经过内核于用户空间mmap同一块内存实现的。而若是你想我同样从2.5内核就关注epoll的话,必定不会忘记手工 mmap这一步的。socket
4.内核微调
这一点其实不算epoll的优势了,而是整个linux平台的优势。也许你能够怀疑linux平台,可是你没法回避linux平台赋予你微调内核的能力。好比,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么能够在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 经过echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也能够根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每一个数据包自己大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。ide
epoll简介函数
在linux的网络编程中,很长的时间都在使用select来作事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增加而下降效率。由于在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,天然耗时越多。而且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,固然,能够经过修改头文件再重编译内核来扩大这个数目,但这彷佛并不治本。ui
epoll的接口很是简单,一共就三个函数:spa
1. int epoll_create(int size);
建立一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不一样于select()中的第一个参数,给出最大监听的fd+1的值。须要注意的是,当建立好epoll句柄后,它就是会占用一个fd值,在linux下若是查看/proc/进程id/fd/,是可以看到这个fd的,因此在使用完epoll后,必须调用close()关闭,不然可能致使fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不一样与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动做,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是须要监听的fd,第四个参数是告诉内核须要监听什么事,struct epoll_event结构以下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events能够是如下几个宏的集合:
EPOLLIN :表示对应的文件描述符能够读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符能够写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来讲的。
EPOLLONESHOT:只监听一次事件,当监听完此次事件以后,若是还须要继续监听这个socket的话,须要再次把这个socket加入到EPOLL队列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,相似于select()调用。参数events用来从内核获得事件的集合,maxevents告以内核这个events有多大,这个maxevents的值不能大于建立epoll_create()时的size,参数timeout是超时时间(毫秒,0会当即返回,-1将不肯定,也有说法说是永久阻塞)。该函数返回须要处理的事件数目,如返回0表示已超时。
下面是我在redhat9上用epoll实现的简单的C/S通讯,已经运行经过了。
server.c
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/epoll.h>
- #define BUFFER_SIZE 40
- #define MAX_EVENTS 10
- int main(int argc, char * argv[])
- {
- int server_sockfd;// 服务器端套接字
- int client_sockfd;// 客户端套接字
- int len;
- struct sockaddr_in my_addr; // 服务器网络地址结构体
- struct sockaddr_in remote_addr; // 客户端网络地址结构体
- int sin_size;
- char buf[BUFFER_SIZE]; // 数据传送的缓冲区
- memset(&my_addr,0,sizeof(my_addr)); // 数据初始化--清零
- my_addr.sin_family=AF_INET; // 设置为IP通讯
- my_addr.sin_addr.s_addr=INADDR_ANY;// 服务器IP地址--容许链接到全部本地地址上
- my_addr.sin_port=htons(8000); // 服务器端口号
- // 建立服务器端套接字--IPv4协议,面向链接通讯,TCP协议
- if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
- {
- perror("socket");
- return 1;
- }
- // 将套接字绑定到服务器的网络地址上
- if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
- {
- perror("bind");
- return 1;
- }
- // 监听链接请求--监听队列长度为5
- listen(server_sockfd,5);
- sin_size=sizeof(struct sockaddr_in);
- // 建立一个epoll句柄
- int epoll_fd;
- epoll_fd=epoll_create(MAX_EVENTS);
- if(epoll_fd==-1)
- {
- perror("epoll_create failed");
- exit(EXIT_FAILURE);
- }
- struct epoll_event ev;// epoll事件结构体
- struct epoll_event events[MAX_EVENTS];// 事件监听队列
- ev.events=EPOLLIN;
- ev.data.fd=server_sockfd;
- // 向epoll注册server_sockfd监听事件
- if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_sockfd,&ev)==-1)
- {
- perror("epll_ctl:server_sockfd register failed");
- exit(EXIT_FAILURE);
- }
- int nfds;// epoll监听事件发生的个数
- // 循环接受客户端请求
- while(1)
- {
- // 等待事件发生
- nfds=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);
- if(nfds==-1)
- {
- perror("start epoll_wait failed");
- exit(EXIT_FAILURE);
- }
- int i;
- for(i=0;i<nfds;i++)
- {
- // 客户端有新的链接请求
- if(events[i].data.fd==server_sockfd)
- {
- // 等待客户端链接请求到达
- if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
- {
- perror("accept client_sockfd failed");
- exit(EXIT_FAILURE);
- }
- // 向epoll注册client_sockfd监听事件
- ev.events=EPOLLIN;
- ev.data.fd=client_sockfd;
- if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_sockfd,&ev)==-1)
- {
- perror("epoll_ctl:client_sockfd register failed");
- exit(EXIT_FAILURE);
- }
- printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr));
- }
- // 客户端有数据发送过来
- else
- {
- len=recv(client_sockfd,buf,BUFFER_SIZE,0);
- if(len<0)
- {
- perror("receive from client failed");
- exit(EXIT_FAILURE);
- }
- printf("receive from client:%s",buf);
- send(client_sockfd,"I have received your message.",30,0);
- }
- }
- }
- return 0;
- }
client.c
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <string.h>
- #include <stdlib.h>
- #define BUFFER_SIZE 40
- int main(int argc, char *argv[])
- {
- int client_sockfd;
- int len;
- struct sockaddr_in remote_addr; // 服务器端网络地址结构体
- char buf[BUFFER_SIZE]; // 数据传送的缓冲区
- memset(&remote_addr,0,sizeof(remote_addr)); // 数据初始化--清零
- remote_addr.sin_family=AF_INET; // 设置为IP通讯
- remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");// 服务器IP地址
- remote_addr.sin_port=htons(8000); // 服务器端口号
- // 建立客户端套接字--IPv4协议,面向链接通讯,TCP协议
- if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
- {
- perror("client socket creation failed");
- exit(EXIT_FAILURE);
- }
- // 将套接字绑定到服务器的网络地址上
- if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
- {
- perror("connect to server failed");
- exit(EXIT_FAILURE);
- }
- // 循环监听服务器请求
- while(1)
- {
- printf("Please input the message:");
- scanf("%s",buf);
- // exit
- if(strcmp(buf,"exit")==0)
- {
- break;
- }
- send(client_sockfd,buf,BUFFER_SIZE,0);
- // 接收服务器端信息
- len=recv(client_sockfd,buf,BUFFER_SIZE,0);
- printf("receive from server:%s/n",buf);
- if(len<0)
- {
- perror("receive from server failed");
- exit(EXIT_FAILURE);
- }
- }
- close(client_sockfd);// 关闭套接字
- return 0;
- }
makefile
- #This is the makefile of EpollTest
- .PHONY:all
- all:server client
- server:
- gcc server.c -o server
- client:
- gcc client.c -o client
- clean:
- rm -f server client