Bootstrap

小试epoll

写这个的原因也很简单,很早以前我会用select,就靠这个在之前的公司里面混了几年,有几个项目都是用的select。现在来看不算啥,不过当时也算高科技了。

后面已知看到说epoll性能更好云云,想试试也很久了,今天终于提起精神试试。。

代码其实还是很简单,如下:

#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl");
        return -1;
    }
    return 0;
}

int main() {
    // 创建epoll实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 假设我们有两个文件描述符:stdin和一个假设的fd
    int fd1 = STDIN_FILENO; // 标准输入
    int fd2 = open("/path/to/some/file", O_RDONLY); // 打开一个文件
    if (fd2 == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 设置文件描述符为非阻塞模式
    set_nonblocking(fd1);
    set_nonblocking(fd2);

    // 将文件描述符添加到epoll实例中
    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET; // 监听输入事件和边缘触发
    event.data.fd = fd1;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd1, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }
    /*event.data.fd = fd2;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd2, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }*/

    // 事件循环
    struct epoll_event events[10];
    while (1) {
        int num_fds = epoll_wait(epoll_fd, events, 10, -1);
        if (num_fds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < num_fds; ++i) {
            if (events[i].events & EPOLLIN) {
                char buf[512];
                int bytes_read = read(events[i].data.fd, buf, sizeof(buf));
                if (bytes_read == -1) {
                    perror("read");
                } else if (bytes_read == 0) {
                    // EOF
                    printf("EOF on fd %d\n", events[i].data.fd);
                    close(events[i].data.fd);
                } else {
                    printf("Read %d bytes on fd %d: %.*s\n", bytes_read, events[i].data.fd, bytes_read, buf);
                }
            }
        }
    }

    close(epoll_fd);
    close(fd2);

    return 0;
}

这个代码是gpt4.0来的,本来以为可以的,结果还是不行。目前的GPT只要稍微偏一点或者复杂一点,出来的代码都没法用。能编过但是没法运行,会报说没有权限。

epoll_ctl: Operation not permitted

查了下原来是epoll_ctl 支持管道,FIFO,套接字,POSIX消息队列,终端,设备等,但是不支持普通文件或目录的fd。把上面的fd2屏蔽了就行了。

其实从应用来看吧,和select几乎是一模一样,就是换了点名字。

再试了一个很简单epoll和共享内存连用的,使用消息进行通知,感觉还可以。

client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define SHM_NAME "my_shm"
#define SHM_SIZE 1024
#define FIFO_NAME "/tmp/my_fifo"

int create_shared_memory() {
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(fd, SHM_SIZE) == -1) {
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }
    return fd;
}

void write_to_shared_memory(int fd, const char *message) {
    void *shm_ptr = mmap(0, SHM_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    strcpy((char *)shm_ptr, message);
    munmap(shm_ptr, SHM_SIZE);
}

int main() {
    int shm_fd = create_shared_memory();
    const char *message = "Hello from shared memory!";
    write_to_shared_memory(shm_fd, message);
    
    int fd = open(FIFO_NAME, O_WRONLY);
    write(fd, "1", 1);
    close(fd);

    close(shm_fd);
    return 0;
}

server

#include <sys/epoll.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define SHM_NAME "my_shm"
#define SHM_SIZE 1024
#define FIFO_NAME "/tmp/my_fifo"

void read_from_shared_memory(int fd) {
    void *shm_ptr = mmap(0, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    printf("Read from shared memory: %s\n", (char *)shm_ptr);
    munmap(shm_ptr, SHM_SIZE);
}

int main() {
    int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    struct epoll_event event;
    event.events = EPOLLIN;
    int fd = open(FIFO_NAME, O_RDONLY);
    event.data.fd = fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[10];
    while (1) {
        int num_fds = epoll_wait(epoll_fd, events, 10, -1);
        if (num_fds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < num_fds; ++i) {
            if (events[i].events & EPOLLIN) {
                printf("receive shm message.\n");
                char buf[1];
                if (read(events[i].data.fd, buf, 1) > 0) {
                    read_from_shared_memory(shm_fd);
                }
            }
        }
    }

    close(epoll_fd);
    close(fd);
    close(shm_fd);

    return 0;
}

运行的话要先

mkfifo /tmp/my_fifo
touch my_shm

这里还是只是搞了一些简单的用法。

如何区别不同的事件呢?

收到event的时候,判断fd类型即可。

fd == events[i].data.fd

再说说水平模式(Level Triggered, LT)和边缘模式(Edge Triggered, ET),这部分我就没试了,总结一点网上的资料。

两个就是数据来的时候,都会报事件。但是水平模式下,只要数据没读取完,就会一直报。水平模式也就是select的模式,开始用LT就好。

最后说说epoll和select区别:

select:适用于文件描述符数量较少的场景,select是 POSIX 标准的一部分,所有具有良好的可移植性,但性能较差。select 有一个文件描述符数量的限制,通常为 1024。这意味着如果要监控的文件描述符超过 1024,必须调整系统参数或使用epoll。每次调用 select都需要将文件描述符集合从用户态复制到内核态,然后在内核中遍历文件描述符集合,这在文件描述符数量较多时会导致性能下降。

epoll:使用一个事件表在内核中记录文件描述符,因此在监控大量文件描述符时性能更高。事件通知采用回调机制,避免了每次调用时的遍历。没有select那样的文件描述符数量限制,能高效地处理大规模的文件描述符。在水平模式下多了一个边缘模式,用得好效率更高。但epoll仅在 Linux 上可用。

参考:

epoll 使用详解(精髓)

Epoll原理解析--网卡接收数据说起

;