一、epoll 相关函数
记不住函数的声明的时候可以在使用man手册查看, man 2 epoll_+函数名字
epoll_create函数声明:
int epoll_create(int size);
功能:在内核中创建epoll事件表,创建一个实例
size:在内核中创建的epoll事件表的大小
return:返回epoll事件表的文件描述符
epoll_ctl函数声明:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:用于控制监听到的某个文件描述符上的事件,可以注册、修改、删除事件
epfd: 由epoll_create生成的epoll操作的文件描述符
op: 对fd文件描述符要进行的操作
EPOLL_CTL_ADD //注册
EPOLL_CTL_MOD //修改
EPOLL_CTL_DEL //删除
fd: 要进行的关联到epoll事件表的文件描述符
return : 成功返回 0,失败返回-1
struct epoll_event *event: event 是指向epoll_even结构体的一个指针,对结构体成员进行操作
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_wait函数声明
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:轮询IO事件的发生,等待epoll事件从epoll实例中发生, 并返回事件以及对应文件描述符。
epfd:由epoll_create生成的epoll专用的文件描述符
struct epoll_event *events:用于回传代处理事件的数组
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 */
};
maxevents:每次能处理的最大事件数
timeout:等待IO事件发生的超时值
return:成功:返回发生的事件数;失败:-1
二、边沿触发和水平触发
epoll事件有两种模型,边沿触发:edge-triggered (ET), 水平触发:level-triggered (LT)
边沿触发仅触发一次,水平触发会一直触发。
水平触发(level-triggered):
socket接收缓冲区不为空 有数据可读 读事件一直触发
socket发送缓冲区不满 可以继续写入数据 写事件一直触发
边沿触发(edge-triggered):
socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件
事件宏:
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将 EPOLL设为边缘触发(Edge Triggered)模式(默认为水平触发),这是相对于水平触发(Level Triggered)来说的。
#define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock', (socket(), bind(), listen()) omitted */
// 创建epoll实例 epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 将监听的端口的socket对应的文件描述符添加到epoll事件列表中 ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
// epoll_wait 阻塞线程,等待事件发生 nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
// 新建的连接 conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
// accept 返回新建连接的文件描述符 if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
// setnotblocking 将该文件描述符置为非阻塞状态
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
// 将该文件描述符添加到epoll事件监听的列表中,使用ET模式 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1)
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
// 使用已监听的文件描述符中的数据 do_use_fd(events[n].data.fd);
}
}
}
示例代码:
//epoll 事件队列
static struct epoll_event events[EPOLL_SIZE];
init();
while (1)
{
//epoll_events_count表示就绪事件的数目
int epoll_events_count = epoll_wait(ep_fd, events, sizeof(events), -1);
if (epoll_events_count < 0)
{
perror(“epoll fail”);
break;
}
cout << "epoll_events_count = " << epoll_events_count << endl;
//处理这些就绪事件
for (int i = 0; i < epoll_events_count; i++)
{
int sock_fd = events[i].data.fd;
if (sock_fd == listen_fd)
{
struct sockaddr_in client_addr;
// (struct sockaddr *)&client_addr, (socklen_t *)sizeof(client_addr)
int client_fd = accept(sock_fd, NULL, NULL);
if (client_fd < 0)
{
perror("accept fail");
exit(-1);
}
cout << "client connection from: "
<< inet_ntoa(client_addr.sin_addr) << ":"
<< ntohs(client_addr.sin_port) << ", client_fd = "
<< client_fd << endl;
addfd(ep_fd, client_fd, true);
//用标准库模板list储存连接的fd
client_list.push_back(client_fd);
cout << "聊天室里新增用户" << client_fd << endl;
cout << "现在总共有: " << client_list.size() << "位用户在聊天室" << endl;
char buf[MAX_SIZE];
memset(buf, 0, sizeof(buf));
sprintf(buf, "欢迎来到在线聊天室! 你的ID是: <Client %d>", client_fd);
int ret = send(client_fd, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("send error");
Close();
exit(-1);
}
}
else
{
int ret = sendMessage(sock_fd);
if (ret < 0)
{
perror("error");
Close();
exit(-1);
}
}
}
}
Close();