一、前言
Epoll 是 Linux I/O 多路复用的管理机制,作为现在Linux 平台高性能网络IO必要的组件,是linux成为主流服务器系统的关键。
I/O多路复用(I/O multiplexing)是一种处理多个I/O事件的机制,允许单个线程同时监听多个I/O通道的数据流。常用的I/O多路复用技术包括select、poll和epoll。它们都允许一个线程同时监视多个文件描述符,当其中任何一个文件描述符的状态发生变化时,线程就会被唤醒,从而实现并发处理多个I/O事件的能力。
二、接口函数
struct epoll_event 结构体
对于struct event 结构体,一般使用两个成员:
events:用户设置需要检测的事件,以及内核需要返回的事件也保存在这里
data.fd:i/o对应的描述符fd
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 */
};
epoll_create 函数
调用epoll_create创建一个epoll对象实例,并返回指向对象实例的文件描述符epfd。
#include <sys/epoll.h>
int epoll_create(int size);
参数size: 无效参数,在linux2.6.8之前,就绪i/o队列使用的是数组,所以参数size指的是能够同时就绪的i/o数量是多少;在linux2.6.8之后,就绪队列使用的是链表,没有了i/o数量的限制,所以参数size就失去了作用,只要传入大于0的数就行。
epoll_ctl 函数
epoll_ctl用来对文件描述符进行添加、修改和删除操作。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- int epfd:指向epoll对象实例的文件描述符epfd
- int op:操作的选项有三种,
EPOLL_ADD:添加fd以及需要检测的事件状态。
EPOLL_DEL:删除fd。
EPOLL_MOD:修改fd对应的要检测的事件状态,一般recv之后改事件为EPOLLOUT检测读事件,send之后改事件为EPOLLIN检测写事件。
- int fd:i/o描述符。
- struct epoll_event *event:携带事件信息,del的时候可设置为NULL。
epoll_wait 函数
调用epoll_wait获取所有就绪的i/o事件以及就绪的I/o数量。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
//返回:所有就绪的i/o数量
- int epfd:指向epoll对象实例的文件描述符epfd
- struct epoll_event *events:用来存放i/o事件信息的结构体数组
- int maxevents:events 结构体数组的对象数量,即能存放的事件数
- int timeout:指示内核等待的时间,-1为永久等待,立即返回,大于0则等待指示的时间数
三、特点分析
epoll特点
epoll 在内核中分有 fd总集 和 fd就绪队列,每次调用epoll_wait 获取 fd就绪队列的fd状态信息即可,不需要进行全部 fd状态检测,也不用将 fd集合从用户空间拷贝到内核空间。
对比 select、poll
四、服务器代码
使用代码请自行设置ip地址和端口,这是一个回射服务器,客户端发什么就回什么。
使用epoll的一般过程
- 调用epoll_create创建epoll文件表,返回指向文件表的epfd
epfd = epoll_create(1);
- 创建struct epoll_events ev 设置读事件,将listenfd加入epoll文件表中
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
- 创建struct epoll_events events[] 用来接收就绪i/o事件
struct epoll_event events[1024] = {0};
- 通过循环的方式调用epoll_wait获取就绪i/o事件
while(1) {
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
}
- 通过EPOLLIN 和EPOLLOUT对读、写事件进行判断
if (events[i].events & EPOLLIN) {}
if (events[i].events & EPOLLOUT) {}
完整代码
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#define SER_PORT 3000
#define BUFFER_SIZE 1024
int epfd = 0;
//接收和发送数据信息
struct conn {
int fd;
char rbuffer[BUFFER_SIZE];
int rlength;
char wbuffer[BUFFER_SIZE];
int wlength;
};
struct conn conn_list[1024] ={0};
void set_event(int fd, int events)
{
struct epoll_event ev;
ev.events = events;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
int init_server(int port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in seraddr;
memset(seraddr, 0, sizeof(struct sockaddr_in));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(port);
if (-1 == bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr))) {
printf("bind failed\n");
}
return sockfd;
}
int main()
{
int sockfd = init_server(SER_PORT);
listen(sockfd,10);
struct sockaddr_in cliaddr;
socklen_t len = sizeof(struct sockaddr_in);
epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
while(1) {
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for (i=0; i<nready; i++) {
int confd = events[i].data.fd;
if (sockfd == confd) {
int clientfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
conn_list[clientfd].fd = clientfd;
} else {
int count = 0;
if (events[i].events & EPOLLIN) {
count = recv(confd, conn_list[confd].rbuffer, BUFFER_SIZE, 0);
if (count == 0) {
printf("client disconnect:%d\n", confd);
close(confd);
epoll_ctl(epfd, EPOLL_CTL_DEL, confd, NULL);
continue;
}
conn_list[confd].rlength = count;
conn_list[confd].wlength = count;
memcpy(conn_list[confd].wbuffer, conn_list[confd].rbuffer, count);
printf("recv:%s fd:%d count:%d\n", conn_list[confd].rbuffer, confd, count);
//将事件置为检测读事件
set_event(confd, EPOLLOUT);
}
if (events[i].events & EPOLLOUT) {
printf("send wbuffer:%s\n", conn_list[confd].wbuffer);
int ret = send(confd, conn_list[confd].wbuffer, conn_list[confd].wlength, 0);
printf("send:%d fd:%d\n", ret, confd);
//将事件置为检测写事件
set_event(confd, EPOLLIN);
}
}
}
}
return 0;
}