这篇文章主要介绍了怎么处理Accept出现Emfile的问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
通常情况下,服务端调用 accept 函数会返回一个新的文件描述符,用于和客户端之间的数据传输
在服务器的开发中,有时会遇到这种情况:当调用 accept 函数接受客户端连接,函数返回失败,对应的错误码是 EMFILE, 它表示当前进程打开的文件描述符已达上限,此时,服务器不能再接受客户端连接
当遇到上述问题,怎么合理的处理呢,下面就来分析一下
建立连接的流程
先简单回顾下客户端和服务器建立连接的流程,具体的如下图所示:
1. 客户端发起 SYN 请求
2. 服务器收到客户端的 SYN 请求后,内核把连接放入半连接队列,同时给客户端返回一个 SYN + ACK
3. 客户端向服务器返回一个确认的 ACK, 服务器收到本次 ACK 之后,三次握手完成,同时,内核把连接从半连接队列中移除,创建新完全连接,加入到全连接队列中
4. 应用层调用 accept 函数从全连接队列中取出连接
上面的第 1、第 2、第 3 步是 TCP 的三次握手,它是由内核中TCP协议完成的, 第 4 步是应用层调用 accept 接口
在 epoll 中的问题
epoll 是 Linux中IO多路复用模型,在服务器的开发中有广泛的应用,下面就以 epoll 为例来详细说明
服务器端创建侦听文件描述符 listenfd 之后, 向 epoll 注册读事件
当 epoll 检测到 listenfd 上有读事件发生,会立即通知应用层,应用层调用 accept 接受新连接,而此时进程打开的文件描述符数量已经达到上限了,所以每次 accept 都是失败的
这里会出现以下几个问题
鸿蒙官方战略合作共建——HarmonyOS技术社区
由于 每次 accept 都失败了,相当于 listenfd 上的可读事件没有处理,epoll 会不停的触发 listenfd 上的可读事件,应用层也就会不停的调用 accept,然后又出现 accept 调用失败,如此这般不停的执行无效的循环,白白浪费了CPU的资源
上面提到服务器在不停的执行无效的循环, 将会引发另一个问题,如果此时有新客户端连接到来,建立连接的过程会很慢
前面说的 epoll 默认是使用了水平触发模式,如果使用垂直触发模式会出现什么问题呢?
垂直触发模式下,listenfd 从无读事件状态到有读事件状态时,才会通知到应用层,在应用层处理完 listenfd 上所有的读事件之前,epoll 不会再通知应用层
也就是说,应用层收到 listenfd 上读事件通知之后,需要把 listenfd 上所有的读事件全部处理完,下次listenfd 上再有读事件时,才会通知应用层
回到 accept 的问题上,在垂直触发模式下,当 epoll 通知应用层 listenfd 上有可读事件时,应用层调用 accept, 由于此时进程打开的文件描述符数量已经达到上限了,所以 accept 调用失败
也即 listenfd 上的可读事件还没有处理,在应用层处理完 listenfd 上可读事件之前,epoll 不会再通知应用层 listenfd 上有可读事件
如果在应用层处理完 listenfd 上可读事件之前,有新的客户端连接到来,这个时候 epoll 是不会通知应用层 listenfd 上有可读事件,这会导致一个严重的问题:accept 只要出现了 EMFILE的错误码,就再也无法接受客户端的连接了
所以,当出现 EMFILE 时,不管使用 epoll 的水平触发模式还是垂直触发模式都会存在问题
如何解决
EMFILE 表示进程打开的文件描述符数量达到上限了,可以把这个值调大些,但这治标不治本
本来系统设置文件描述符数量上限是为了限制进程对系统资源的过度占用,况且,这个值调整到多大合适呢,总不能无限大吧,所以调整上限值的方式不是最合适的方式
accept 成功时会返回一个新的文件描述符,如果此时进程打开的文件描述符数量已经达到上限了,就会返回失败
假如此时能关闭一个空闲的文件描述符,让出一个名额,再调用 accept 就会创建成功,这种方式具体的处理步骤如下:
1、事先准备一个空闲的文件描述符 idlefd,相当于先占一个"坑"位
2、调用 close 关闭 idlefd,关闭之后,进程就会获得一个文件描述符名额
3、再次调用 accept 函数, 此时就会返回新的文件描述符 clientfd, 立刻调用 close 函数,关闭 clientfd
4、重新创建空闲文件描述符 idlefd,重新占领 "坑" 位,再出现这种情况的时候又可以使用
由于测试代码比较长,这里就不贴了,感兴趣可以通过文末的方式获取,下面是处理 EMFILE 的伪代码:
int ret = accept( listenfd, (struct sockaddr*)&addr, sizeof(addr) ); if (-1 == ret) { if ( errno == EMFILE ) { //关闭空闲文件描述符,释放 "坑"位 close(idlefd); //接受 clientfd clientfd = accept( listenfd, nullptr, nullptr); //关闭 clientfd,防止一直触发 listenfd 上的可读事件 close(clientfd); //重新占领 "坑"位 idlefd = ::open("/dev/null", O_RDONLY | O_CLOEXEC); } }
感谢你能够认真阅读完这篇文章,希望小编分享的“怎么处理Accept出现Emfile的问题”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。