Linux内核IO多路复用之epoll简介

描述

epoll简介

epoll是Linux内核为处理大量句柄而改进的poll,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

epoll的工作方式

LT(level triggered):水平触发,缺省方式,在这种方式中,内核告诉我们一个文件描述符是否就绪了,如果就绪了,就可以对描述符进行IO操作。如果不做任何操作,内核还是会继续通知。

ET(edge-triggered):边沿触发,在这种方式下,当描述符从未就绪变为就绪状态时,内核通知应用。但是如果一直不对这个描述符做IO操作,内核不会再发送通知。

区别: 边沿触发仅触发一次,水平触发会一直触发。

epoll相关函数

epoll主要有epoll_create, epoll_ctl, epoll_wait 3个系统调用。

epoll_create

int epoll_create(int size)

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

epoll的事件注册函数,在这里先注册要监听的事件类型。

epfd: epoll句柄

op: 表示动作,有如下三个动作:

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
fd: 要监听的描述符。

event: 要监听的事件,struct epoll_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;

events可设置的值如下:

EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式
EPOLLONESHOT:只监听一次事件,当监听结束之后,要继续监听的话,需要再次加入到EPOLL队列里

epoll_wait

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)
等待事件的到来。

events: 分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中。

maxevents: events的大小。

timeout: 超时时间(毫秒,0会立即返回,-1将是永久阻塞)。

如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

使用例子


/* net_epoll.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define PORT 4321
#define MAX_QUE_CONN_NM 5
#define EPOLL_SIZE 10 //epoll监听的客户端的最大数目,Linux2.6.8之后该参数可以忽略
#define BUFFER_SIZE 1024

int main()
{
struct sockaddr_in server_sockaddr, client_sockaddr;
int sin_size, count;
int epfd;
struct epoll_event ev, events[EPOLL_SIZE];
int event_cnt;
int sockfd, client_fd;
char buf[BUFFER_SIZE];
//创建socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
//设置IP,端口号
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY; //任意地址,也就是表示本机的所有IP
bzero(&(server_sockaddr.sin_zero), 8);
int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
//绑定
if (bind(sockfd, (struct sockaddr *)&server_sockaddr,
sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
//开始监听
if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}

printf("listening....\n");

//创建一个epoll描述符,并将监听socket加入epoll
if((epfd = epoll_create(EPOLL_SIZE)) == -1)
{
perror("epoll_create");
exit(1);
}
else
{
ev.events = EPOLLIN | EPOLLOUT | EPOLLET; //读写事件,边沿触发
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
}

while(1)
{
sin_size = sizeof(struct sockaddr_in);
memset(buf, 0, sizeof(buf));
/*调用 epoll_wait()等待事件到来*/
if ((event_cnt = epoll_wait(epfd, events, EPOLL_SIZE, -1)) <= 0)
{
perror("epoll_wait");
}

for (i = 0; i < event_cnt; i++)
{
//判断来事件的是否是监听连接的socket
if (events[i].data.fd == sockfd)
{ /* 服务端接收客户端的连接请求 */
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, (socklen_t *)&sin_size))== -1)
{
perror("accept");
exit(1);
}
//将新连接的socket放进去
ev.data.fd = client_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
printf("New connection from %d(socket)\n", client_fd);
}
else /* 处理从客户端发来的消息 */
{
if ((count = recv(events[i].data.fd, buf, BUFFER_SIZE, 0)) > 0)
{
printf("Received a message from %d: %s\n",
events[i].data.fd, buf);
}
else
{
close(events[i].data.fd);
ev.data.fd = events[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
printf("Client %d(socket) has left\n", events[i].data.fd);
}
}
} /* end of for fd*/
} /* end if while while*/

close(sockfd);
exit(0);
}

总结

epoll的优点是支持大数目的描述符,IO效率不随描述符数目增加而线性下降。所以在高并发网络中应用比较多,一般是在服务端。



审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分