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、线程池和定时器,可以实现更复杂和高效的网络或系统事件处理。

相关推荐
Maki Winster2 分钟前
Peek-Ubuntu上Gif录制工具-24.04LTS可装
linux·ubuntu·peek
Maki Winster1 小时前
在 Ubuntu 下配置 oh-my-posh —— 普通用户 + root 各自使用独立主题(共享可执行)
linux·运维·ubuntu
守望时空331 小时前
Linux下KDE桌面创建自定义右键菜单
linux
l0sgAi2 小时前
vLLM在RTX50系显卡上部署大模型-使用wsl2
linux·人工智能
麟城Lincoln3 小时前
【RHCSA-Linux考试题目笔记(自用)】servera的题目
linux·笔记·考试·rhcsa
寻月隐君3 小时前
保姆级教程:Zsh + Oh My Zsh 终极配置,让你的 Ubuntu 终端效率倍增
linux·后端·命令行
XM-54583 小时前
2025微信小程序wxapkg解包全攻略
linux·运维·小程序
朗晴4 小时前
文本编辑器VIM的使用方法!
linux·运维·服务器
2401_8260976212 小时前
JavaEE-Linux环境部署
java·linux·java-ee
(:满天星:)13 小时前
第31篇:块设备与字符设备管理深度解析(基于OpenEuler 24.03)
linux·运维·服务器·网络·centos