Linux基础 -- epoll监听Netlink并实现

使用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 多路复用的强大工具,结合高级用法如 EPOLLETEPOLLONESHOT、线程池和定时器,可以实现更复杂和高效的网络或系统事件处理。

相关推荐
orion571 天前
Missing Semester Class1:course overview and introduction of shell
linux
用户120487221612 天前
Linux驱动编译与加载
linux·嵌入式
用户805533698032 天前
Input 子系统架构:Core、Handler、Driver 三层是怎么协作的
linux·嵌入式
用户805533698032 天前
RK-Forge外设系列开篇 - 把板子从「能启动」变成「能用」:Ethernet/SPI/MMC 三个纯接线外设
linux·github·嵌入式
七歌杜金房2 天前
我终于又有了自己的 Linux 电脑
linux·debian·mac
tntxia3 天前
linux curl命令详解_curl详解
linux
扛枪的书生4 天前
Linux 网络管理器用法速查
linux
顺风尿一寸4 天前
Java Socket 内核之旅:从 SocketChannel.read() 到 tcp_recvmsg 与 epoll 的完整调用链路
linux
XIAOHEZIcode4 天前
Ubuntu 终端美化全栈指南:Bash 到 Kitty 踩坑实录
linux·ubuntu·命令行
唐青枫4 天前
别再只会用 cron:Linux systemd Timer 定时任务实战详解
linux