本文转载自微信公众号《Linux开发那些事儿》,作者LinuxThings。转载此文请联系Linux开发那些事公众号.通常,服务端调用accept函数返回一个新的文件描述符,用于客户端与服务端之间的数据传输。在服务端开发中,有时会出现这种情况:调用accept函数接受客户端连接时,函数返回失败,对应的错误码为EMFILE,表示当前进程打开的文件描述符有达到了上限。此时,服务器不能再接受客户端连接。遇到以上问题,如何合理处理?分析建立连接的过程。我们简单回顾一下客户端与服务端建立连接的过程,如下图所示:1.客户端发起SYN请求。2、服务器收到客户端的SYN请求后,内核将连接放入一半连接队列,同时返回一个SYN+ACK给客户端。客户端向服务器返回确认ACK。服务器收到这个ACK后,三次握手就完成了。同时,内核从半连接队列中移除连接并创建一个新的连接。完成连接,加入全连接队列4、应用层调用accept函数从全连接队列中取出连接。上面的第1、2、3步是TCP的三次握手,由内核中的TCP协议完成。第四步,应用层调用epoll中的accept接口。epoll是Linux中的一种IO多路复用模型。它广泛用于服务器开发。下面以epoll为例详细说明如何在server端创建监听文件。在描述符listenfd之后,向epoll注册读取事件。当epoll检测到listenfd上有read事件时,会立即通知应用层,应用层调用accept接受新的连接。此时进程打开的文件描述符数量已经达到上限。所以每次accept失败,都会出现下面的问题。因为每次accept失败,就意味着listenfd上的可读事件没有处理,epoll会不断触发listenfd上的可读事件,应用层也会一直调用accept,然后accept调用就会失败,这样不停的执行无效循环,白白浪费CPU资源。上面说到服务端不断的在执行无效循环,这会导致另外一个问题,如果这个时候有新的客户端连接到来,那么建立连接的过程会很慢。事件达到读取事件的状态,应用层处理在listenfd上所有read事件发生之前,epoll不会再通知应用层。也就是说,应用层收到listenfd上的读事件通知后,需要处理所有listenfd上的读事件,下次有listenfd上的读事件才会通知应用层返回接受问题。在垂直触发方式下,当epoll通知应用层listenfd上有可读事件时,应用层调用accept,因为进程打开的文件描述符数量已经达到上限,所以accept调用失败,即listenfd上的可读事件还没有处理。在应用层处理listenfd上的可读事件之前,epoll不会通知应用层listenfd上有可读事件。如果在应用层处理listenfd上的可读事件在listenfd上的可读事件发生之前,有一个新的客户端连接。此时epoll不会通知应用层listenfd上有可读事件。这会造成一个严重的问题:只要accept中出现EMFILE错误码,就会重启,也无法接受来自客户端的连接。所以当出现EMFILE时,无论你使用epoll的水平触发方式还是垂直触发方式都会出现问题。怎么解决EMFILE意思是进程打开的文件描述符数量已经达到上限,可以增加这个值。但这是治标不治本。本来系统设置文件描述符数量的上限是为了限制进程过度占用系统资源。而且,这个值调整到什么程度才合适呢?不能无限大,所以调整上限的方式不是最好的合适的方法accept成功后会返回一个新的文件描述符。如果此时进程打开的文件描述符数量已经达到上限,则返回失败。如果此时可以关闭一个空闲的文件描述符,放弃一个quota,然后调用accept创建成功。该方法具体处理步骤如下:1、提前准备一个空闲文件描述符idlefd,相当于先占一个“坑”。2.调用close关闭idlefd。关闭后进程会启动一个文件描述符配额3.再次调用accept函数,此时会返回新的文件描述符clientfd,立即调用close函数关闭clientfd4,重新创建空闲的文件描述符idlefd,重新占据“坑”的位置,然后当发生这种情况时,又可以使用了。由于测试代码比较长,这里就不贴出来了。有兴趣的可以在文末获取。下面是如何处理EMFILE的伪代码:intret=accept(listenfd,(structsockaddr*)&addr,sizeof(addr));if(-1==ret){if(errno==EMFILE){//关闭空闲文件描述符,释放“”pit"bitclose(idlefd);//acceptclientfdclientfd=accept(listenfd,nullptr,nullptr);//关闭clientfd,防止listenfd上的可读事件一直被触发close(clientfd);//重新占用"pit"位idlefd=::open("/dev/null",O_RDONLY|O_CLOEXEC);}}
