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

相关推荐
努力学习的少女6 分钟前
linux/centOS7用户和权限管理笔记
linux·运维·笔记
绿白尼23 分钟前
Linux C所有预定义的宏
linux·c语言
安於宿命27 分钟前
【Linux】文件系统
linux·服务器·c++
xiegwei28 分钟前
rocky linux 安装 android studio 并运行 模拟器
linux·运维·android studio
我们的五年1 小时前
【Linux课程学习】:第20弹---信号入门专题(基础部分)
linux·服务器·后端·学习·缓存
Camellia-Echo1 小时前
【Linux从青铜到王者】数据链路层(mac,arp)以及ip分片
linux·运维·服务器
小白—人工智能1 小时前
Linux —— vim 编辑器
linux·编辑器·vim
qichengzong_right1 小时前
CNCF云原生生态版图-分类指南(一)- 观测和分析
linux·云原生
linux修理工2 小时前
centos 7 升级内核到4.19
linux·运维·centos
jekc8682 小时前
Ubuntu安装grafana
linux·ubuntu·grafana