首先援引http://blog.csdn.net/xiajun07061225/article/details/9250579的一些东西,紧接着给出个人的观点和代码,如有不足,请各位大神补充更正
三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好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结构如下:
- //保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
- typedef union epoll_data {
- void *ptr;
- int fd;
- __uint32_t u32;
- __uint64_t u64;
- } epoll_data_t;
- //感兴趣的事件和被触发的事件
- 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);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
linux下epoll如何实现高效处理百万句柄的
开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢?
使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。
epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。
epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。
从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。
所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。
个人观点:首先,当A用户向服务器发送数据时,服务器内核红黑树会有一个节点(插入节点时给节点绑定了一个event事件)与之建立连接;当有数据到达时,操作系统会将这个event事件返回到用户空间的event数组中,(ep[1024]数组);event数组封装了文件描述符和回调函数,服务器可以通过访问event的回调函数来响应A的请求。
efd = epoll_create(1024);//在此创建一个句柄,执行后会在内核创建一颗红黑树
#include<stdlib.h>
//#include<sys/socket.h>
#define SERVPORT 8000
void main()
{
int sockfd,cfd,efd;
int i,j;
char buf[1024];
struct sockaddr_in serv_addr,cli_addr;
struct epoll_event tep,ep[1024];
bzero(&serv_addr,sizeof(struct sockaddr_in));
bzero(&cli_addr,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.port = htons(SERVPORT);
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("socket create error\n");
exit(0);
}
if((bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
{
perror("bind error\n");
exit(0);
}
if(listen(sockfd,128) < 0)
{
perror("listen error\n");
exit(0);
}
efd = epoll_create(1024);//在此创建一个句柄,执行后会在内核创建一颗红黑树
tep.events = EPOLLIN;
tep.data.fd = sockfd;
int res = epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&tep);//插入一个lfd(即listen监听的套接字描述符),此处是sockfd;
while(1)
{
int nready = epoll_wait(efd,ep,1024,-1);
for(i = 0;i<nready;i++)
{
if(ep[i].data.fd == sockfd)//如果是新的监听套接字到来,则执行以下操作
{
int cli_len = sizeof(cli_addr);
cfd = accept(sockfd,(struct sockaddr_in *)&cli_addr,&cli_len);//在此返回一个与特定请求相关的套接字描述符,用于通信
if(cfd < 0)
{
perror("acept error\n");
exit(0);
}
tep.events = EPOLLIN;
tep.data.fd = cfd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&tep);//将此socket句柄塞入内核事先创建的红黑树中,此时仍然有一个lfd(即listen监听的套接字描述符)
}
else//如果是红黑树中已经存在的套接字,则执行以下操作
{
sockfd = ep[i].data.fd;
int n = read(sockfd,buf,1024);
if(n==0)//超时,从红黑树删除对应套接字
{
res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
close(sockfd);
printf("client[%d] closed connection\n",j);
}
else//将小写变大写,返回给客户端
{
for(j=0;j<n;j++)
buf[j] = toupper(buf[j]);
write(sockfd,buf,n);
}
}
}
}
return;
}