一、epoll函数组解析
1、epoll_create函数
函数原型
int epoll_create(int size)
参数解释
(1)size:此参数在现在已经没有是什么意义了
(2)返回值:返回值为一个文件描述符,作为后面两个函数的参数
函数作用
此函数可以在内核中创建一个内核事件表,通过返回的内核事件表来管理
2、epoll_ctl函数
函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
参数解释
(1)epfd:操作内核时间表的文件描述符,即epoll_create函数的返回值
(2)op:操作内核时间表的方式
有以下操作方式:
(1) EPOLL_CTL_ADD(向内核时间表添加文件描述符,即注册)
(2) EPOLL_CTL_MOD(修改内核事件表事件)
(3) EPOLL_CTL_DEL (删除内核事件表中的事件)
(3)fd:操作的文件描述符
(4)event:指向struct epoll_event的指针
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;
结构体变量解释
(1)events:储存用户感兴趣的事情和就绪事件
(2)data:为一个联合体,联合体最重要的就是fd,即要操作的文件描述符
events取值
(1)EPOLLOUT:表示对应的文件描述符可以写;
(2)EPOLLPRI:表示对应的文件描述符有紧急的数据可读
(3)EPOLLERR:表示对应的文件描述符发生错误;
(4)EPOLLHUP:表示对应的文件描述符被挂断;
(5)EPOLLET:表示对应的文件描述符设定为edge模式;
函数作用
此函数可以增,删,改内核事件表中的事件
3、epoll_wait函数
函数原型
int epoll_wait(int epfd, struct epoll_event events, int maxevents, int timeout)
参数解释
(1)epfd:同上面函数
(2)events:用于接收内核返回的就绪事件的数组
(3)maxevents:用户最多能处理的事件个数
(4)等待I/O的超时值(后面的编程设为-1,表示永不超时),单位为ms
(5)返回值,指的是就绪事件的个数
函数作用
此函数在内核中实现,若有就绪事件,内核就会向用户返回已就绪的事件(注意:这里与poll不同,pol只返回已就绪事件的个数)
二、epoll编程代码及注释
#define _GNU_SOURCE
#define MAXFD 100
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int create_socket()//创建一个socket连接
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(-1 != listenfd);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(listenfd, (struct sockaddr *)&ser, sizeof(ser));
assert(-1 != res);
listen(listenfd, 5);
return listenfd;
}
void get_client_link(int fd, int epfd)//获取一个客户端连接
{
struct sockaddr_in cli;
int len = sizeof(cli);
int c = accept(fd,(struct sockaddr_in *)&cli, &len);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = c;
epoll_ctl(epfd, EPOLL_CTL_ADD, c, &ev);
printf("one client link\n");
}
void unlink_client(int fd, int epfd)//断开连接
{
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
printf("one client unlink\n");
}
void deal_client_data(int fd, int epfd)//处理客户端数据
{
int buff[128] = {0};
int res = recv(fd, buff, 127, 0);
if(res == 0)
{
unlink_client(fd, epfd);
return;
}
printf("%s\n",buff);
send(fd, "ok", 2, 0);
}
void deal_finish_fd(int n, struct epoll_event *events, int epfd, int listenfd)//处理就绪的文件描述符
{
int i = 0;
for(i = 0;i <= n;i++)
{
int fd = events[i].data.fd;
if(fd == listenfd)
{
get_client_link(fd, epfd);
}
else if(events[i].events & EPOLLHUP)
{
unlink_client(fd, epfd);
}
else if(events[i].events & EPOLLIN)
{
deal_client_data(fd, epfd);//在后面,如果n = 0,需要断开连接,所以得把epfd传进去
}
else
{
printf("fd error\n");
}
}
}
int main()
{
int listenfd = create_socket();
int epfd = epoll_create(5);
struct epoll_event ev;
ev.data.fd = listenfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);//在内核事件中注册listenfd
while(1)
{
struct epoll_event events[MAXFD];
int n = epoll_wait(epfd, events, MAXFD, -1);
if(n < 0)
{
printf("epoll error\n");
break;
}
deal_finish_fd(n, events, epfd, listenfd);
}
exit(0);
}
当然这是份儿服务器代码,如果要检测的话得需要一分儿客户端代码,这这里就不过多的赘述了,
有兴趣的读者可以自己编写测试一下,一下是我的测试结果:
三、epoll的优缺点
1、支持打开大数目的socket描述符(相对于select)
2、I\O效率不随fd数目的增加而线性下降(因为每次内核返回的是已就绪的文件描述符)
以上就是epoll的使用以及代码,欢迎读者纠错