使用epoll监听Netlink并实现高级用法
本文档主要介绍如何使用 epoll
监听 Netlink
消息,包括基础实现与高级用法。
epoll监听Netlink的基础实现
以下示例展示了如何通过 epoll
监听 Netlink
消息并处理收发。
功能说明
- 创建一个
Netlink
套接字。 - 使用
epoll
监听来自Netlink
的消息。 - 收到消息后进行处理并回送消息。
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#include <sys/epoll.h>#define NETLINK_USER 31
#define MAX_PAYLOAD 1024 // 最大负载
#define EPOLL_MAX_EVENTS 10int main() {struct sockaddr_nl src_addr, dest_addr;struct nlmsghdr *nlh = NULL;struct iovec iov;struct msghdr msg;int sock_fd, epoll_fd;struct epoll_event ev, events[EPOLL_MAX_EVENTS];// 创建netlink套接字sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);if (sock_fd < 0) {perror("socket creation failed");return -1;}memset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid(); // 用户进程IDsrc_addr.nl_groups = 0; // 不订阅多播组// 绑定套接字if (bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0) {perror("bind failed");close(sock_fd);return -1;}// 初始化epollepoll_fd = epoll_create1(0);if (epoll_fd < 0) {perror("epoll_create1 failed");close(sock_fd);return -1;}ev.events = EPOLLIN; // 可读事件ev.data.fd = sock_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &ev) < 0) {perror("epoll_ctl failed");close(sock_fd);close(epoll_fd);return -1;}// 设置netlink消息结构nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);nlh->nlmsg_pid = getpid();nlh->nlmsg_flags = 0;iov.iov_base = (void *)nlh;iov.iov_len = nlh->nlmsg_len;memset(&msg, 0, sizeof(msg));msg.msg_name = (void *)&dest_addr;msg.msg_namelen = sizeof(dest_addr);msg.msg_iov = &iov;msg.msg_iovlen = 1;// 循环监听事件while (1) {int nfds = epoll_wait(epoll_fd, events, EPOLL_MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {if (events[i].data.fd == sock_fd) {// 接收消息recvmsg(sock_fd, &msg, 0);printf("Received message payload: %s\n", (char *)NLMSG_DATA(nlh));// 回送消息strcpy((char *)NLMSG_DATA(nlh), "Reply from user space");sendmsg(sock_fd, &msg, 0);}}}// 清理资源close(sock_fd);close(epoll_fd);free(nlh);return 0;
}
epoll的高级用法
1. 使用 EPOLLET
(边沿触发模式)
边沿触发模式只在状态发生变化时触发事件,适合高性能场景。
代码示例
ev.events = EPOLLIN | EPOLLET; // 边沿触发
ev.data.fd = sock_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &ev);while (1) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {if (events[i].events & EPOLLIN) {char buf[1024];while (1) {ssize_t n = read(events[i].data.fd, buf, sizeof(buf));if (n <= 0) {if (errno == EAGAIN) break;perror("read");close(events[i].data.fd);break;}printf("Read %ld bytes: %s\n", n, buf);}}}
}
2. 使用 EPOLLONESHOT
EPOLLONESHOT
避免同一事件被多个线程处理,触发后需要重新注册。
代码示例
ev.events = EPOLLIN | EPOLLONESHOT;
ev.data.fd = sock_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &ev);void handle_event(int epoll_fd, int fd) {printf("Handling event for fd %d\n", fd);struct epoll_event ev;ev.events = EPOLLIN | EPOLLONESHOT;ev.data.fd = fd;epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
}
3. 并发处理(多线程)
通过线程池分发事件,提高处理效率。
代码示例
void *worker_thread(void *arg) {int fd = *(int *)arg;char buf[1024];read(fd, buf, sizeof(buf));printf("Worker thread handled fd %d: %s\n", fd, buf);
}void epoll_main_loop(int epoll_fd) {struct epoll_event events[MAX_EVENTS];while (1) {int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {pthread_t tid;pthread_create(&tid, NULL, worker_thread, &events[i].data.fd);pthread_detach(tid);}}
}
4. 使用定时器
结合 timerfd
实现高效的超时处理。
代码示例
#include <sys/timerfd.h>int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
struct itimerspec new_value = {.it_value = {5, 0}, // 5秒后触发.it_interval = {0, 0}
};
timerfd_settime(tfd, 0, &new_value, NULL);struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = tfd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tfd, &ev);if (events[i].data.fd == tfd) {uint64_t expirations;read(tfd, &expirations, sizeof(expirations));printf("Timer expired %llu times\n", expirations);
}
总结
epoll
是高性能 I/O 多路复用的强大工具,结合高级用法如 EPOLLET
、EPOLLONESHOT
、线程池和定时器,可以实现更复杂和高效的网络或系统事件处理。