epoll特点
epoll 的主要特点包括:
1.没有文件描述符数量限制:与 select 和 poll 不同,epoll 采用了基于事件的就绪通知机制,没有预定义的文件描述符数量限制,可以支持更大规模的并发连接。
2.高效的事件通知:epoll 使用了内核和用户空间共享的事件数据结构,将文件描述符的事件注册到内核空间,当事件就绪时,内核直接将就绪的事件通知给用户空间,避免了每次调用都需要遍历整个文件描述符数组的性能开销。
3.分离的就绪事件集合:epoll 将就绪的事件从内核空间复制到用户空间,形成一个分离的就绪事件集合,用户可以直接遍历这个集合来处理就绪的事件,而不需要遍历整个文件描述符数组。
4.支持边缘触发和水平触发:epoll 提供了两种模式来处理事件,一种是边缘触发模式(EPOLLET),只在状态发生变化时通知应用程序,另一种是水平触发模式(默认),在事件就绪期间一直通知应用程序。
5.更低的内存拷贝开销:epoll 使用内存映射技术,避免了每次调用都需要将事件数据从内核复制到用户空间的开销,从而减少了系统调用的次数和内存拷贝的开销。
函数说明
epoll_create
创建epoll实例,后续epoll操作关联此epoll实例(close(2), epoll_ctl(2), epoll_wait(2))
#include <sys/epoll.h>
/****************************************************************************
函 数 名 : epoll_create
功能描述 : 创建epoll实例
输入参数 : int size > 0 Linux 2.6.8后此值无用
输出参数 :
返 回 值 : int -1 失败,并设置错误码 非0 epoll实例文件描述符
**************************************************************************/
int epoll_create(int size);
epoll_ctl
操作选型
- EPOLL_CTL_ADD 添加
- EPOLL_CTL_MOD 修改
- EPOLL_CTL_DEL 删除
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 */
};
监听的事件
- EPOLLIN 0x01 可读
- EPOLLOUT 0x04 可写 tcp连上触发一次,套接字可写触发
- EPOLLRDHUP 0x2000 对端关闭连接或kill
- EPOLLPRI 0x02 epoll文件描述符异常
- EPOLLERR 0x08 关联文件描述符上发生错误情况,没必要设置因为出现会报
- EPOLLHUP 0x10 在关联的文件描述符上发生挂起, 没必要设置因为出现会报
- EPOLLET 0x80000000 边缘触发
- EPOLLONESHOT 0x40000000
- EPOLLWAKEUP 0x20000000
- EPOLLEXCLUSIVE 0x10000000
#include <sys/epoll.h>
/****************************************************************************
函 数 名 : epoll_ctl
功能描述 : epoll实例操作
输入参数 : int epfd epoll实例描述符
int op 操作选项
int fd 描述符
struct epoll_event *event 关联描述符的事件对象
输出参数 :
返 回 值 :int -1 失败 并设置错误码 0成功
**************************************************************************/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_wait
阻塞等待关联描述符就绪,timeout-1 永久阻塞知道关联描述符就绪,timeout>0超时时间
描述符就绪时会将就绪描述符置于 events中,可遍历列表处理,很高效
#include <sys/epoll.h>
/****************************************************************************
函 数 名 : epoll_wait
功能描述 : epoll监听关联描述符事件
输入参数 : int epfd epoll实例描述符
int maxevents 监听描述符最大数
int timeout 超时时间 ms
输出参数 : struct epoll_event *events 事件就绪列表
返 回 值 :int -1 失败 0成功
**************************************************************************/
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
源码实例
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#define SERVER_PORT 9999
#define SERVER_LISTEN_NUM 10
#define TIMEOUT_SEC 5
#define TIMEOUT_USEC 0
#define SOKCETLIST_MAX SERVER_LISTEN_NUM+1
/*
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct timeval {
long tv_sec; seconds
long tv_usec; microseconds
};
*/
typedef struct
{
int use;
int sock;
}SocketList_t;
SocketList_t g_sock_list[SOKCETLIST_MAX];
int socketlist_init(void)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
g_sock_list[i].use = 0;
g_sock_list[i].sock = -1;
}
}
int socketlist_add(int sock)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (g_sock_list[i].use)
{
continue;
}
g_sock_list[i].use = 1;
g_sock_list[i].sock = sock;
return 0;
}
return -1;
}
void socketlist_del(int sock)
{
int i = 0;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (g_sock_list[i].use && g_sock_list[i].sock == sock)
{
g_sock_list[i].use = 0;
g_sock_list[i].sock = -1;
return;
}
}
}
int socketlist_fdmax_get(void)
{
int i = 0;
int maxfd = -1;
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (!g_sock_list[i].use)
{
continue;
}
maxfd = g_sock_list[i].sock >= maxfd ? g_sock_list[i].sock : maxfd;
}
return maxfd;
}
void sock_init_before_select(fd_set *readset, fd_set *writeset, fd_set *errorfds)
{
int i = 0;
if (readset)
{
FD_ZERO(readset);
}
if (writeset)
{
FD_ZERO(writeset);
}
if (errorfds)
{
FD_ZERO(errorfds);
}
for (i = 0; i < SOKCETLIST_MAX; i++)
{
if (!g_sock_list[i].use)
{
continue;
}
if (readset)
{
FD_SET(g_sock_list[i].sock, readset);
}
if (writeset)
{
FD_SET(g_sock_list[i].sock, writeset);
}
if (errorfds)
{
FD_SET(g_sock_list[i].sock, errorfds);
}
}
}
/***************************
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 位掩码,可多个事件(EPOLLIN EPOLLOUT EPOLLRDHUP
EPOLLPRI EPOLLERR EPOLLHUP EPOLLET EPOLLONESHOT EPOLLWAKEUP EPOLLEXCLUSIVE)
epoll_data_t data; // User data variable
};
*******************************/
int main(void *arg, char *argv[])
{
int i = 0;
int sock = -1;
int listen_sock = -1;
int ret = 0;
int epoll_fd_num = 0;
struct sockaddr_in address;
struct sockaddr_in remote_addr;
socklen_t remote_addrlen = sizeof(remote_addr);
unsigned long optval = 1;
unsigned char recv_buf[256];
int epoll_instacne = 0;
struct epoll_event event;
struct epoll_event events[SOKCETLIST_MAX];
printf("EPOLLIN:%x, EPOLLOUT:%x, EPOLLRDHUP:%x, EPOLLPRI:%x, EPOLLERR:%x, EPOLLHUP:%x, EPOLLET:%x, EPOLLONESHOT:%x,EPOLLWAKEUP:%x, EPOLLEXCLUSIVE:%x\r\n",
EPOLLIN, EPOLLOUT, EPOLLRDHUP, EPOLLPRI, EPOLLERR, EPOLLHUP, EPOLLET, EPOLLONESHOT,EPOLLWAKEUP, EPOLLEXCLUSIVE);
socketlist_init();
sock = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sock)
{
printf("socket fail\r\n");
return -1;
}
//一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
address.sin_family = AF_INET;
address.sin_port = htons(SERVER_PORT);
address.sin_addr.s_addr = INADDR_ANY;
ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
if (-1 == ret)
{
printf("bind fail\r\n");
return -1;
}
ret = listen(sock, SERVER_LISTEN_NUM);
if (-1 == ret)
{
printf("listen fail\r\n");
return -1;
}
epoll_instacne = epoll_create(1);
if (-1 == epoll_instacne)
{
printf("epoll_create fail %s\r\n", strerror(errno));
return -1;
}
socketlist_add(sock);
event.events = EPOLLIN;
event.data.fd =sock;
ret = epoll_ctl(epoll_instacne, EPOLL_CTL_ADD, sock, &event);
while (1)
{
epoll_fd_num = epoll_wait(epoll_instacne, events, SOKCETLIST_MAX, -1);
if (-1 == epoll_fd_num)
{
printf("epoll_wait fail %s\r\n", strerror(errno));
return -1;
}
else if (0 == epoll_fd_num)
{
printf("epoll_wait timeout\r\n");
continue;
}
for (i = 0; i < epoll_fd_num; i++)
{
if (events[i].data.fd == sock)
{
listen_sock = accept(sock, (struct sockaddr*)&remote_addr, &remote_addrlen);
event.events = EPOLLIN | EPOLLRDHUP | EPOLLET | EPOLLOUT;
event.data.fd =listen_sock;
epoll_ctl(epoll_instacne, EPOLL_CTL_ADD, listen_sock, &event);
}
else
{
if (events[i].events & EPOLLRDHUP || events[i].events & EPOLLERR) //对端关闭连接或异常
{
socketlist_del(events[i].data.fd);
epoll_ctl(epoll_instacne, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
printf("client close event:%x\r\n", events[i].events);
continue;
}
else if (events[i].events & EPOLLIN)
{
memset(recv_buf, 0, sizeof(recv_buf));
ret = recv(events[i].data.fd, recv_buf, sizeof(recv_buf), 0);
if (-1 == ret)
{
printf("recv fail:%s, %d", strerror(errno), events[i].events);
socketlist_del(events[i].data.fd);
epoll_ctl(epoll_instacne, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
continue;
}
printf("recv ret:%d, data:%s\r\n", ret, recv_buf);
}
}
}
}
}