Linux—多路转接之epoll

在Linux中,epoll是一种高效的I/O事件通知机制,特别适用于处理大量并发连接的网络服务。与传统的selectpoll系统调用相比,epoll能更好地应对大规模文件描述符的管理,其背后的设计理念是将I/O事件的监听和文件描述符的管理分离,从而提高性能。

epoll主要有三个系统调用: 1. epoll_create:创建一个epoll实例。 2. epoll_ctl:控制epoll实例,可以添加、修改或删除要监控的文件描述符。 3. epoll_wait:等待事件的发生,并返回就绪的文件描述符。

epoll使用示例

以下是一个使用epoll的简单 TCP 服务器示例,它能够同时处理多个客户端的连接请求:

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

#define PORT 8080
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

// 设置非阻塞模式
int set_non_blocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    int server_fd, client_fd, epoll_fd;
    struct sockaddr_in address;
    int opt = 1;
    socklen_t addrlen = sizeof(address);

    // 创建 socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置 socket 选项
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 设置地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定 socket
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听 socket
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    struct epoll_event event, events[MAX_EVENTS];

    // 将 server_fd 添加到 epoll 实例中
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);

    // 设置服务器 socket 为非阻塞
    set_non_blocking(server_fd);

    while (1) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // 等待事件发生
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 处理新的客户端连接
                client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen);
                if (client_fd < 0) {
                    perror("accept");
                    continue;
                }
                set_non_blocking(client_fd);

                // 将 client_fd 添加到 epoll 实例中
                event.events = EPOLLIN | EPOLLET; // 使用边缘触发
                event.data.fd = client_fd;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
                printf("新连接: %d\n", client_fd);
            } else {
                // 处理客户端消息
                char buffer[BUFFER_SIZE];
                int nbytes = read(events[i].data.fd, buffer, sizeof(buffer));
                if (nbytes <= 0) {
                    // 客户端关闭连接
                    close(events[i].data.fd);
                    printf("关闭连接: %d\n", events[i].data.fd);
                } else {
                    // 处理收到的数据
                    buffer[nbytes] = '\0'; // 确保字符串结束
                    printf("收到来自 %d 的消息: %s\n", events[i].data.fd, buffer);
                    // 这里对数据进行处理,比如回写
                    write(events[i].data.fd, buffer, nbytes);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

代码解析

  1. socket创建:使用socket函数创建一个TCP服务器。
  2. socket选项和绑定:设置socket选项,使其可复用,并将其绑定到指定的IP地址和端口。
  3. epoll实例:通过epoll_create1创建一个epoll实例,然后将server_fd添加到epoll监视列表中。
  4. 事件循环:程序进入事件循环,调用epoll_wait等待事件的到来。
  5. 处理连接和消息:当有新的连接到来时,接收连接并将其添加到epoll监视列表中。当有客户端发送消息时,读取数据并进行处理。

总结

epoll的使用可以极大地提高Linux系统中对大量连接的处理效率。通过合理使用非阻塞I/O和边缘触发模式,可以构建出高性能的网络应用。在高并发的场景中,epoll相较于传统的I/O多路复用方法如selectpoll,显得尤为重要。

点赞(0) 打赏

微信小程序

微信扫一扫体验

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部