epoll介绍
epoll的实现和select与poll的实现有很大的差异,epoll不像select和poll一样通过一个系统调用来完成任务,通过一组函数,epoll把用户关心的文件描述符的事件放在内核的一个事件表中,这样就不用每次都进行向内核传递文件描述符了。epoll使用一个额外的文件描述符来表示内核的事件表,所以这里第一个函数epoll_create就是做这个的。
epoll_create
- 1
- 1
size现在并不起作用,给内核提示,告诉内核事件表有多大。这个函数返回的文件描述符用作其他的epoll相关函数的参数。指向要访问的内核事件表。
然后,我们又有一个函数就是epoll_ctl。
epoll_ctl
- 1
- 1
这个函数用来进行向事件表注册事件,参数epfd就是我们epoll_create函数的返回值,
op是操作类型,有三种。
op参数 | 说明 |
---|---|
EPOLL_CTL_ADD | 向事件表中注册fd上的事件 |
EPOLL_CTL_MOD | 修改fd上的注册事件 |
EPOLL_CTL_DEL | 删除fd上的注册事件 |
第三个参数是epoll_event结构指针类型,我们首先来看epoll_event结构。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
这里的events也是一个即是输入型参数,又是输出型参数,你所关心的监听事件,你jinx设置events,然后如果发生事件,你也可以从events进行获得。
events的参数和poll中的类似,前面加了E。
events参数 | 说明 |
---|---|
EPOLLIN | 表示对应的文件描述符可以读(包括对端SOCKET正常关闭) |
EPOLLOUT | 表示对应的文件描述符可以写 |
EPOLLPRI | 表示对应的文件描述符有紧急-的数据可读(这里应该表示有带外数据到来) |
EPOLLERR | 表示对应的文件描述符发生错误 |
EPOLLHUP | 表示对应的文件描述符被挂断 |
EPOLLET | 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 |
EPOLLONESHOT | 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里1 |
后面两个参数EPOLLET和EPOLLONESHOT是epoll独有的,它们提供给了epoll高效的操作,后续进行介绍。
data是一个结构:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个共用体提供了一些成员来供用户使用。一般来说使用最多的是fd,它指定事件所属的目标文件描述符。ptr成员可以用来指定与fd相关的用户数据。但是这是一个联合,所以我们想要实现一个快速的数据访问,我们可以采取其他办法,比如说我们创建一个结构体,让ptr指向这个结构体,这个结构体当中保存这数据和fd。
最后,我们来说一下epoll_wait
epoll_wait
- 1
- 2
- 3
- 1
- 2
- 3
epoll来进行等待文件描述符的事件。这个函数成功的话,返回就绪文件描述符的数量,失败返回-1.
第一个参数epfd,就是epoll_create所得到的返回值,也就是内核事件表。然后第二个参数是结构体数组,这个结构体元素的结构我们在上面讲epoll_ctl的时候已经说过。这个数组用于输出epoll_wait检测到的就绪事件,并不是一个即输入,又输出的参数,这样就可以大大地提高了程序找到就绪文件描述符的效率。maxevents指的是监听事件个数,也就是数组的大小。最后的timeout,设置超时时间,参数与poll的是一样的。
epoll的实际上,当你进行create的时候,这个时候内核进行准备你要监控的文件描述符,然后当是哟个epoll_ctl的使用,其实就是往内核中继续加入新的文件描述符。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
static void Usage(const char *str)
{
printf("Usage:%s [serv_ip][serv_port]\n",str);
}
typedef struct fd_buf //struct epoll_event ev ev.data.ptr 指向这个结构体。
{
int fd;
char buf[10240]; //每一个套接字都有自己的缓冲区。
}fd_buf_t,*fd_buf_p;
static void* alloc_fd_buf(int fd)
{
fd_buf_p tmp = (fd_buf_p)malloc(sizeof(fd_buf_t));
if(tmp == NULL)
{
perror("malloc");
return NULL;
}
tmp->fd = fd;
return tmp;
}
static int startup(const char *ip,int port)
{
int listen_socket = socket(AF_INET,SOCK_STREAM,0);
if(listen_socket < 0)
{
perror("socket");
exit(1);
}
int op = 1;
setsockopt(listen_socket,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(op)); //端口复用
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(ip);
serv_addr.sin_port = htons(port);
int ret = bind(listen_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr) );
if(ret < 0)
{
perror("bind");
exit(2);
}
ret = listen(listen_socket,128);
if(ret < 0)
{
perror("listen");
exit(3);
}
return listen_socket;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int epollfd = epoll_create(256);//建议操作系统创建一个有256个节点的红黑树。
if(epollfd < 0)
{
perror("epoll_create");
close(listen_sock);
return 5;
}
struct epoll_event ev;
ev.events = EPOLLIN; //监听listen_socket的可读事件。
ev.data.ptr = alloc_fd_buf(listen_sock); //每一个fd都分配自己的空间。
int ret = epoll_ctl(epollfd,EPOLL_CTL_ADD,listen_sock,&ev);//将listen_sock加入到红黑树中,并且监听它的可读事件。
int nums = 0;
struct epoll_event evs[64];
int timeout = -1;
while(1)
{
switch( (nums = epoll_wait(epollfd,evs,64,timeout) ) ) //红黑树中就绪的节点放入循环队列中,把队列中的事件放入epoll_event数组中。它不会修改红黑树的节点和定时器,所以不需要每次都重新初始化。
{
case -1:
perror("epoll_wait");
break;
case 0:
printf("timeout....!\n");
break;
default:
{
int i = 0;
for(; i < nums; ++i) //遍历数组,这个数组的元素都是就绪的。
{
fd_buf_p fp = (fd_buf_p)evs[i].data.ptr; //ptr指向一个结构,这个结构中包含了一个文件描述符和一块空间。
if(fp->fd == listen_sock &&(evs[i].events & EPOLLIN))
{
struct sockaddr_in clie_addr;
socklen_t len = sizeof(clie_addr);
int new_sock = accept(listen_sock, (struct sockaddr*)&clie_addr,&len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new client\n");
struct epoll_event temp;
temp.events = EPOLLIN;
temp.data.ptr = alloc_fd_buf(new_sock); //将连接套接字加入到红黑树中
epoll_ctl(epollfd,EPOLL_CTL_ADD,new_sock,&temp);
}//if
else if(fp->fd != listen_sock)
{
if(evs[i].events & EPOLLIN) //可读事件。
{
ssize_t s = read(fp->fd,fp->buf,sizeof(fp->buf)-1); //
if(s > 0)
{
fp->buf[s] = 0; //从标准输入中读取s需要减一,其他情况下不需要。
printf("client say:%s\n",fp->buf);
//读完后让这个套接字成为写状态。
struct epoll_event temp;
temp.events = EPOLLOUT;
temp.data.ptr = fp;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fp->fd,&temp);
}
else if (s <= 0)
{
close(fp->fd);
epoll_ctl(epollfd,EPOLL_CTL_DEL,fp->fd,NULL);
free(fp);
}
} //if 可读事件。
else if(evs[i].events & EPOLLOUT) //可写事件。
{
const char *msg = "HTTP/1.0 200 OK\r\n\r\n<html><h1> hello epoll!</h1></html>";
write(fp->fd,msg, strlen(msg));
close(fp->fd);
epoll_ctl(epollfd,EPOLL_CTL_DEL,fp->fd,NULL);
free(fp);
}
}
} //for finish;
break;
} //default finish
}
}
return 0;
}
截图:
(1)首先关闭防火墙:
(2)服务器启动:
(3)浏览器访问