使用epoll监听Netlink并实现高级用法
本文档主要介绍如何使用 epoll
监听 Netlink
消息,包括基础实现与高级用法。
epoll监听Netlink的基础实现
以下示例展示了如何通过 epoll
监听 Netlink
消息并处理收发。
功能说明
- 创建一个
Netlink
套接字。 - 使用
epoll
监听来自Netlink
的消息。 - 收到消息后进行处理并回送消息。
示例代码
c
#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 10
int 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(); // 用户进程ID
src_addr.nl_groups = 0; // 不订阅多播组
// 绑定套接字
if (bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0) {
perror("bind failed");
close(sock_fd);
return -1;
}
// 初始化epoll
epoll_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
(边沿触发模式)
边沿触发模式只在状态发生变化时触发事件,适合高性能场景。
代码示例
c
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
避免同一事件被多个线程处理,触发后需要重新注册。
代码示例
c
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. 并发处理(多线程)
通过线程池分发事件,提高处理效率。
代码示例
c
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
实现高效的超时处理。
代码示例
c
#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
、线程池和定时器,可以实现更复杂和高效的网络或系统事件处理。