前言
epoll作为Linux特有的IO多路复用的模型,是select/poll的改进版,用于监控大量的文件描述符。另一个特点是,epoll全程为event poll,即该接口是基于事件触发的,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
函数介绍
- 创建epoll监控树根节点句柄
/*创建一个epoll的句柄。size为epoll所支持的最大句柄数*/
int epoll_create(int size);
- 等待监听
/*描述:等待事件触发
epfd:创建的epoll句柄
events:储存所有的已触发读写事件
maxevents:最大事件数量
timeout:超时,-1为永久等待*/
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
- 添加/修改/删除需要侦听的文件描述符及其事件
/*
epfd:创建的epoll句柄
op: 操作,包络增加,删除和修改
fd:监听的句柄
event:->events 事件触发类型,水平,边缘触发
->data 用户参数
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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 */
};
任何被侦听的文件符只要被关闭,那么它也会自动从被侦听的文件描述符集合中删除;要尽量少地调用epoll_ctl,防止其引来的开销
epoll event类型有如下三个:
EPOLL_CTL_ADD:添加
EPOLL_CTL_MOD:修改
EPOLL_CTL_DEL:删除
- epoll_ctl函数中的参数 struct epoll_event结构体介绍
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
该参数表示要监控的fd的事件触发类型参数;参数1可以为如下:
enum EPOLL_EVENTS
{
EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
EPOLLWAKEUP = 1u << 29,
#define EPOLLWAKEUP EPOLLWAKEUP
EPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOT
EPOLLET = 1u << 31
#define EPOLLET EPOLLET
};
参数2的epoll_data_t data是一个联合体,定义如下
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
一般用void* ptr来自定义参数。该参数在该文件句柄触发是返回给用户。所以可以用此参数来自定义返回值,例如如下定义
typedef int (*on_event)(int fd, void* argv);
struct _userdata{
int fd; /*文件句柄*/
on_event onevent; /*毁掉函数*/
on_event onerr; /*错误返回*/
void* argv; /*参数*/
int status; /*当前fd状态*/
}
可以用于定义不同的fd使用不同的处理方法。
简单实例代码
/**
* @file epoll_srv.c
* @brief epoll测试
* @details
* @mainpage
* @author Y Locki
* @email chaodong_y@aliyun.com
* @version V1.0.0.0
* @date 2019-10-21日
* @license Y Locki
*/
#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include<errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "epoll_srv.h"
int setfd_nblk(int fd)
{
int flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK;
return fcntl(fd, F_SETFL, flag);
}
int main(int argc, char** argv)
{
if (argc != 3) {
printf("usage: epoll_srv ip port\n");
return -1;
}
int ret = 0;
int epollfd = 0;
struct epoll_event ev = { 0 }; /**< epoll 节点事件 */
struct epoll_event readyev[10] = { 0 }; /**< 就绪节点事件 */
const char* ip = argv[1];
unsigned short port = atoi(argv[2]);
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in addr = { 0 };
inet_aton(ip, &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (0 != (ret = bind(fd, (struct sockaddr*) & addr, sizeof(addr)))) {
goto SOCK_EXIT;
}
if (0 != (ret = listen(fd, MAX_CLIENT))) {
goto SOCK_EXIT;
}
ret = setfd_nblk(fd);
/*添加epoll事件*/
epollfd = epoll_create(MAX_CLIENT);
ev.events = EPOLLIN | EPOLLET; // 边沿触发
// 添加私有地址
ev.data.fd = fd;
if (0 != (ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev))) {
printf("epoll_ctl failed errno is :%d", errno);
close(epollfd);
goto SOCK_EXIT;
}
while (1) {
int readys = epoll_wait(epollfd, readyev, 10, -1);
for (int i = 0; i < readys; i++) {
if (readyev[i].data.fd == fd) {
// 收到新链接
struct sockaddr_in cli = { 0 };
socklen_t len = sizeof(cli);
int clifd = 0;
if (-1 == (clifd = accept(fd, (struct sockaddr*)&cli, &len))) {
printf("accept failed, errno = %d\n", errno);
continue;
}
printf("new client ip = %s, fd=%d\n", inet_ntoa(cli.sin_addr), clifd);
ret = setfd_nblk(clifd);
struct epoll_event evc = { 0 };
evc.events = EPOLLIN | EPOLLET;
evc.data.fd = clifd;
setfd_nblk(clifd);
if (0 != (ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, clifd, &evc))) {
printf("epoll_ctl fd:%d failed errno is :%d", clifd, errno);
}
}
else {
// 收到消息触发
char recvbuf[2048] = { 0 };
int recvlen = 0;
while (1) {
ret = recv(readyev[i].data.fd, recvbuf + recvlen, sizeof(recvbuf) - recvlen, 0);
if (ret < 0) {
if (errno == EAGAIN) {
break;
}else {
printf("recv failed %d\n", errno);
break;
}
}else if (ret == 0)
{
printf("peer disconnected\n");
if (0 != (ret = epoll_ctl(epollfd, EPOLL_CTL_DEL, readyev[i].data.fd, NULL))) {
printf("epoll_ctl fd:%d failed errno is :%d", readyev[i].data.fd, errno);
}
break;
}else {
recvlen += ret;
}
}
printf("fd=%d recv buf:%s len:%d \n", readyev[i].data.fd, recvbuf, recvlen);
}
}
}
SOCK_EXIT:
close(fd);
return 0;
}
/**
* @file epoll_srv.h
* @brief epoll 测试头文件
* @details
* @mainpage
* @author Y Locki
* @email chaodong_y@aliyun.com
* @version V1.0.0.0
* @date 2019-10-21日
* @license Y Locki
*/
#define MAX_CLIENT 10000
typedef struct _epdata epdata_t;
struct _epdata {
int fd;
int flag;
};