• 优雅的处理 accept= -1 出现errno = EMFILE 文件描述符达到上限 的问题


    优雅的处理 accept= -1 出现errno = EMFILE 文件描述符达到上限 的问题

    前言

      优雅的处理 accept= -1 出现errno = EMFILE 文件描述符达到上限 的问题

      本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

    什么情况下会出现errno = EMFILE

      在Posix API 与 网络协议栈 详细介绍 一文中,我们知道了accpet函数只做两件事:1. 从全连接队列里面取出一个TCB结点 2. 为这个TCB结点分配一个fd,把fd和TCB做一个一对一对应的关系。

    在这里插入图片描述
      accept= -1,errno=EMFILE只有一个原因,那就是文件描述符达到上限,一个进程能够打开的文件描述符的个数是有上限的。

      所以这就衍生出了一个非常简单粗暴的解决方案:提高上限,即把一个进程能够打开的文件描述符的个数通过Linux内核参数调整,那么进程能够连接的个数自然就上去了。具体操作方法参考Linux服务器百万并发实现与问题排查一文中的error : Too many open files

      但是我们要想一下,这种方法真的可靠吗?虽然暂时的解决了问题,那么等连接又达到了上限后,怎么办呢,我们总不能无限制的去提高上限吧。所以这个方案治标不治本。

    优雅的解决方案

      说实话,在我没有思考之前,我的第一反应是,accept出错就出错呗,continue就好了,不管这个连接了。但是这样是行不通的,我们可以看到上图,该TCB一直存在全连接队列里面,说明什么?epoll水平触发会一直返回 listenfd 的读事件(epoll的实现原理),而现在fd不够用,又continue,随后又触发读事件,又continue。这就形成了无效的循环。简单来说,如果不处理掉这个TCB,epoll会不停的触发listenfd的读事件。造成无效的循环流程,浪费CPU资源

      如果变成epoll边沿触发呢?边沿触发配对非阻塞和while的流程。比如现在全连接队列有10个待取的连接,而fd已经达到了上限。此时第一个TCB在accept的时候出现了EMFILE错误,那么必然是break。在没有新的连接进入之前,epoll不会再触发listenfd的读回调,那也就代表着,如果程序这个时候前面有很多连接都close掉了,而全连接队列里面的连接因为第一个连接被break,导致后面的9个待取的连接都无法执行accept的流程。这也是有问题的,也就是说,不论ET还是LT,出现errno = EMFILE,我们都需要去解决。

      假设现在fd的上限是1024,我们可以限制连接数,对连接的个数计数,最大为1000,那么在1001个连接到来时,我们可以先accept这个连接(上限是1024,那么1001肯定不会出错),然后发生一个消息给对端:send(fd,"Connection reached the upper limit"),随后断开这个连接close(fd)。这样一来,我们即处理了这个连接,又没有出错。

      如果在业务场景没有特别标注需要限制连接数的时候,我们肯定不会像上面的做法一样,浪费24个文件描述符不用。我们肯定是越能压榨机器越好,也就是说,我们对上面进行改进,我们预先霸占一个文件描述符idleFd,而不是进行连接计数,在遇到errno = EMFILE 的时候,先close(idleFd),此时程序就会有一个空闲的文件描述符供accept使用,那么clientfd=accept(),然后再把这个clientfd关掉close(clientfd),最后把原来的idleFd恢复。如此一来,是最为优雅的解决方案。

    1. 事先准备一个空闲的文件描述符 idlefd
    2. close(idlefd) ,此时程序就会有一个空闲的文件描述符供accept使用
    3. clientfd = accept()
    4. send(clientfd ,“Connection reached the upper limit”)
    5. close(clientfd)
    6. 恢复idlefd的文件描述符

    那么用代码来写就是如下的部分代码了。

    //水平触发
    int idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);
    
    int accept_cb(int fd, int events, void *arg) {
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        socklen_t client_len = sizeof(client_addr);
    
        int clientfd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
        if (clientfd == -1 && errno == EMFILE) {
            close(idleFd);
            idleFd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
            
            send(idleFd, "Connection reached the upper limit", sizeof("Connection reached the upper limit"), 0);
            
            close(idleFd);
            idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //边沿触发
    int idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);
    
    int accept_cb(int fd, int events, void *arg) {
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        socklen_t client_len = sizeof(client_addr);
    
        while (1) {
            int clientfd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
            if (clientfd == -1) {
                if (errno == EMFILE) {
                    close(idleFd);
                    idleFd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
                    send(idleFd, "Connection reached the upper limit", sizeof("Connection reached the upper limit"), 0);
                    close(idleFd);
                    idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);
                }
                else if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    return;
                }
            }else{
                //正常流程
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    什么是网络存储服务器
    1507. 转变日期格式
    JavaScript:实现binarySearch二分查找算法(附完整源码)
    什么企业可以申报高新技术企业
    (三)DepthAI-python相关接口:OAK Nodes
    基于hadoop MapReduce的新闻平台用户点击视频统计分析
    初识Scrapy和使用
    Lumiprobe 聚乙二醇化和 PEG 接头丨碘-PEG3-酸研究
    SpringBoot核心注解
    子选择器(重点)
  • 原文地址:https://blog.csdn.net/qq_42956653/article/details/126396993