怎么处理Accept出现Emfile的问题

这篇文章主要介绍了怎么处理Accept出现Emfile的问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

公司主营业务:成都做网站、成都网站制作、成都外贸网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。成都创新互联是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。成都创新互联推出顺德免费做网站回馈大家。

通常情况下,服务端调用 accept 函数会返回一个新的文件描述符,用于和客户端之间的数据传输

服务器的开发中,有时会遇到这种情况:当调用 accept 函数接受客户端连接,函数返回失败,对应的错误码是 EMFILE,  它表示当前进程打开的文件描述符已达上限,此时,服务器不能再接受客户端连接

当遇到上述问题,怎么合理的处理呢,下面就来分析一下

建立连接的流程

先简单回顾下客户端和服务器建立连接的流程,具体的如下图所示:

怎么处理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 都是失败的

这里会出现以下几个问题

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 由于 每次 accept 都失败了,相当于 listenfd 上的可读事件没有处理,epoll 会不停的触发 listenfd  上的可读事件,应用层也就会不停的调用 accept,然后又出现 accept 调用失败,如此这般不停的执行无效的循环,白白浪费了CPU的资源

  3. 上面提到服务器在不停的执行无效的循环, 将会引发另一个问题,如果此时有新客户端连接到来,建立连接的过程会很慢

前面说的 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的问题”这篇文章对大家有帮助,同时也希望大家多多支持创新互联,关注创新互联行业资讯频道,更多相关知识等着你来学习!


分享题目:怎么处理Accept出现Emfile的问题
网页网址:http://ybzwz.com/article/jpcdsh.html