• 模拟epoll的饥饿场景


    说明

    一直听说epoll的饥饿场景,但是从未在实际环境中面对过,那么能不能模拟出来呢?实际的情况是怎样呢?

    模拟步骤

    • 基于epoll写一个简单的tcp echo server,将每次read返回的字节数打印出来
    • 模拟一个客户端大量写入
    • 测试其他客户端能否正常返回

    Server代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAX_EVENTS 1024
    #define LISTEN_BACKLOG 10
    
    int epoll_fd;
    void do_read(int fd);
    
    int main() {
        int server_fd, nfds, i;
        struct epoll_event event, events[MAX_EVENTS];
        struct sockaddr_in server_addr, client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_fd;
    
        // 创建 socket
        server_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (server_fd == -1) {
            perror("socket");
            return 1;
        }
    
        // 设置 socket 选项
        int opt = 1;
        if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
            perror("setsockopt");
            close(server_fd);
            return 1;
        }
    
        // 绑定 socket
        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(8080);
        if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
            perror("bind");
            close(server_fd);
            return 1;
        }
    
        // 监听 socket
        if (listen(server_fd, LISTEN_BACKLOG) == -1) {
            perror("listen");
            close(server_fd);
            return 1;
        }
    
        // 创建 epoll 实例
        epoll_fd = epoll_create1(0);
        if (epoll_fd == -1) {
            perror("epoll_create1");
            close(server_fd);
            return 1;
        }
    
        // 注册服务器 socket
        event.events = EPOLLIN;
        event.data.fd = server_fd;
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
            perror("epoll_ctl");
            close(server_fd);
            close(epoll_fd);
            return 1;
        }
    
        printf("Server listening on port 8080...\n");
    
        while (1) {
            // 等待事件就绪
            nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
            if (nfds == -1) {
                perror("epoll_wait");
                close(server_fd);
                close(epoll_fd);
                return 1;
            }
    
            // 处理就绪事件
            for (i = 0; i < nfds; i++) {
                if (events[i].data.fd == server_fd) {
                    // 接受新连接
                    client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
                    if (client_fd == -1) {
                        perror("accept");
                        continue;
                    }
    
                    if (fcntl(client_fd , F_SETFL, O_NONBLOCK) == -1) {
                        perror("fcntl");
                        close(client_fd);
                        continue;
                    }
                    // 注册客户端 socket
                    event.events = EPOLLIN;
                    event.data.fd = client_fd;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                        perror("epoll_ctl");
                        close(client_fd);
                        continue;
                    }
    
                    printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                } else {
                    do_read(events[i].data.fd);
                }
            }
        }
    
        close(server_fd);
        close(epoll_fd);
        return 0;
    }
    
    void do_read(int fd) {
        // 处理客户端数据
        char buf[1024];
        while(1) {
            ssize_t bytes_read = read(fd, buf, sizeof(buf));
            if (bytes_read == -1) {
                perror("read");
                close(fd);
                if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
                    perror("epoll_ctl");
                }
                break;
            } else if (bytes_read == 0) {
                printf("Client disconnected\n");
                close(fd);
                if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
                    perror("epoll_ctl");
                }
                break;
            } else {
                printf("Received data: %d\n", bytes_read);
                if (write(fd, buf, bytes_read) != bytes_read) {
                    perror("write");
                    close(fd);
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
                        perror("epoll_ctl");
                    }
                    break;
                }
                if (bytes_read < 1024) {
                    break;
                }
            }
        }
    }
    
    

    模拟客户端

    客户端1:大量写入客户端:

    cat /dev/random 2>/dev/null | nc 127.0.0.1 8080 >/dev/null
    

    客户端2:其他写入客户端,少量写入检查返回值

    nc 127.0.0.1 8080
    

    模拟结果

    • server端收到大量的数据,每次read返回1024个字节,句柄非常忙碌
      image
    • 客户端2往server发送的数据一直没有返回【处于饥饿状态】
      image
    • 一旦客户端1断开,客户端2就收到回复了
      image

    结果分析

    从代码中可以知道,read一直都有数据读取,一直在处理数据,导致其他句柄无法处理数据。也就是说,其实是我们的代码造成了所谓的饥饿,那么也可以从我们的代码层面上去解决这个问题,思路官方man page中已经提到了,将fd维护一个list,均匀的读写数据即可。

  • 相关阅读:
    Python下划线中 _xx、__xx、__xx__ 的区别
    js基础语法和结构和示例代码(1-10个语法)
    跨境电商必知的交叉销售和追加销售:2022终极指南
    [Codeforces] games (R1200) Part.3
    双向链表的基本操作
    【练习题】二.栈和队列
    数据结构小记【Python/C++版】——栈篇
    HDFS、YARN、MapReduce概述及三者之间的关系
    perl 下判断文件和目录是否为空?
    Typescript语言基础
  • 原文地址:https://www.cnblogs.com/bymzy/p/18244598