xd96dx Docs
  • c/c++
    • 网络编程
      • linux 网络
        • linux 网络io模型
        • Signal-Driven I/O Model(信号驱动)
        • Asynchronous I/O Model(异步IO)
      • IO多路复用
        • select 示例
        • poll 示例
        • epoll 示例
      • Reactor实例之 muduo 源码分析
    • stl实现
      • 容器(Containers)
        • Vector(动态数组)
        • List(双向链表)
        • Deque(二级动态数组)
  • 游戏开发
    • skynet 从demo到源码
      • server-client demo
      • skynet 源码分析-启动流程之初始化
      • skynet 源码分析-启动流程之创建service
      • skynet 源码分析-启动流程之线程池启动
Powered by GitBook
On this page
  • code
  • epoll 的性能优势来源:
  1. c/c++
  2. 网络编程
  3. IO多路复用

epoll 示例

code

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <cstring>
#include <vector>

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

// 创建非阻塞 socket
int create_server_socket() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "Socket creation failed!" << std::endl;
        return -1;
    }

    // 设置 socket 为非阻塞模式
    int flags = fcntl(server_fd, F_GETFL, 0);
    fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);

    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Binding failed!" << std::endl;
        close(server_fd);
        return -1;
    }

    if (listen(server_fd, 3) < 0) {
        std::cerr << "Listen failed!" << std::endl;
        close(server_fd);
        return -1;
    }

    return server_fd;
}

int main() {
    int server_fd = create_server_socket();
    if (server_fd == -1) {
        return 1;
    }

    // 创建 epoll 实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        std::cerr << "Epoll create failed!" << std::endl;
        close(server_fd);
        return 1;
    }

    // 将 server_fd 加入 epoll 监听
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
        std::cerr << "Epoll_ctl failed!" << std::endl;
        close(server_fd);
        close(epoll_fd);
        return 1;
    }

    std::vector<struct epoll_event> events(MAX_EVENTS);

    while (true) {
        // 等待事件发生
        int num_events = epoll_wait(epoll_fd, events.data(), MAX_EVENTS, -1);  // 阻塞直到有事件
        if (num_events == -1) {
            std::cerr << "Epoll wait failed!" << std::endl;
            break;
        }

        // 处理所有发生的事件
        for (int i = 0; i < num_events; ++i) {
            if (events[i].data.fd == server_fd) {
                // 新的客户端连接
                sockaddr_in client_addr;
                socklen_t client_len = sizeof(client_addr);
                int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
                if (client_fd == -1) {
                    std::cerr << "Accept failed!" << std::endl;
                } else {
                    std::cout << "New client connected!" << std::endl;

                    // 设置新连接为非阻塞
                    int flags = fcntl(client_fd, F_GETFL, 0);
                    fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);

                    // 将 client_fd 添加到 epoll 中
                    event.events = EPOLLIN | EPOLLET;  // 使用边缘触发
                    event.data.fd = client_fd;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                        std::cerr << "Epoll_ctl failed!" << std::endl;
                        close(client_fd);
                    }
                }
            } else if (events[i].events & EPOLLIN) {
                // 客户端发送数据
                int client_fd = events[i].data.fd;
                char buffer[BUFFER_SIZE];
                ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer));
                if (bytes_read > 0) {
                    std::cout << "Received data: " << std::string(buffer, bytes_read) << std::endl;
                    // 向客户端发送数据
                    write(client_fd, "Hello, client!", 14);
                } else if (bytes_read == 0) {
                    // 客户端关闭连接
                    std::cout << "Client disconnected!" << std::endl;
                    // 从 epoll 中删除 client_fd
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr) == -1) {
                        std::cerr << "Failed to remove client_fd from epoll!" << std::endl;
                    }
                    close(client_fd);
                } else {
                    std::cerr << "Read error!" << std::endl;
                    close(client_fd);
                }
            }
        }
    }

    // 清理资源
    close(server_fd);
    close(epoll_fd);
    return 0;
}

epoll 的性能优势来源:

  1. 事件的通知机制

  • epoll 则采用了更高效的事件通知机制。当一个事件发生时,内核只会通知应用程序,而无需每次遍历整个文件描述符集合。epoll 通过一个基于事件回调的机制,只有在有事件发生时,才将相关的文件描述符通知给应用程序。这样,应用程序无需每次都遍历整个文件描述符集合,而只需要关心那些真正有事件的文件描述符

  • poll/select 通过传递一个文件描述符集合(fd_set)来通知哪些文件描述符发生了事件。在每次调用时,内核都必须遍历这个文件描述符集合,并检查每个描述符的状态,哪怕没有任何事件发生,这个遍历的过程是线性的,时间复杂度是 O(n),其中 n 是文件描述符的数量。

  1. 文件描述符管理

  • epoll 用了基于事件的通知机制,称为 水平触发(Level Triggered, LT) 和 边缘触发(Edge Triggered, ET)。通过 epoll_ctl 将文件描述符添加到 epoll 的监听队列中,epoll 会记录文件描述符的状态,且只有在文件描述符的状态变化时,内核才会通知应用程序,避免了不必要的遍历和重复通知

  • select/poll 如果你要监听多个文件描述符,文件描述符数量上限通常是固定的(如 FD_SETSIZE),并且在添加、删除文件描述符时,每次都需要重新传递整个文件描述符集合给内核,因此,在动态增加或删除文件描述符时,会有大量的用户态到内核态的拷贝

  1. 事件通知的优化

  • epoll 提供水平触发(Level Triggered, LT) 和 边缘触发(Edge Triggered, ET)两种模式

  • select/poll 仅支持 水平触发

Previouspoll 示例NextReactor实例之 muduo 源码分析

Last updated 6 months ago