通常情况下,服务端调用 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 都是失败的

这里会出现以下几个问题

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

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

前面说的 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 的问题的更多相关文章

  1. linux 网络编程 基础

    网络编程基础 套接字编程需要指定套接字地址作为参数,不同的协议族有不同的地址结构,比如以太网其结构为sockaddr_in. 通用套接字: struct sockaddr { sa_family_t ...

  2. accept 文件描述符用尽处理

    if (events[i].data.fd == listenfd) { peerlen = sizeof(peeraddr); connfd = ::accept4(listenfd, (struc ...

  3. Java8之——简洁优雅的Lambda表达式

    Java8发布之后,Lambda表达式,Stream等等之类的字眼边慢慢出现在我们字眼.就像是Java7出现了之后,大家看到了“钻石语法”,看到了try-with-resource等等.面对这些新东西 ...

  4. 如何优雅的设计React组件

    如何优雅的设计 React 组件 如今的 web 前端已被 React.Vue 和 Angular 三分天下,一统江山十几年的 jQuery 显然已经很难满足现在的开发模式.那么,为什么大家会觉得 j ...

  5. java~lambda表达式让查询更优雅

    在java之前的版本里,如果希望从集合时查找符合条件的数据,如果先遍历他,这种写法是我们不能接受的,所以现在java有了lambda就很好的解决了这个问题,让代码更优雅一些! /** * lambda ...

  6. Golang的优雅重启

    更新(2015年4月):Florian von Bock已将本文中描述的内容转换为一个名为endless的优秀Go包 . 如果您有Golang HTTP服务,可能需要重新启动它以升级二进制文件或更改某 ...

  7. JDK8漫谈——代码更优雅

    简介 lambda表达式,又称闭包(Closure)或称匿名方法(anonymous method).将Lambda表达式引入JAVA中的动机源于一个叫"行为参数"的模式.这种模式 ...

  8. [译]Golang中的优雅重启

    原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加. ...

  9. 接口处理篇 accept bind connect atan2 htons inet_addr inet_aton inet_ntoa listen ntohl recv send sendto socket

    accept(接受socket连线) 相关函数 socket,bind,listen,connect 表头文件 #include<sys/types.h> #include<sys/ ...

随机推荐

  1. 删除数组中指定的元素,然后将后面的元素向前移动一位,将最后一位设置为NULL 。 String[] strs={“aaa”,”ccc”,”ddd”,”eee”,”fff”,”ggg”}; 指定删除字符串“ccc”,把后的元素依次向前移动!!!

    public static void main(String[] args) { int temp = -1; String[] strs = {"aaa", "ccc& ...

  2. Docker安装GitLab与Runner(网关),常规设置,自动化用到k8s+token

    [转]图文详解k8s自动化持续集成之GitLab CI/CD Windows里面使用Debian命令行工具完成 和Docker网络相关的命令 查看某一个容器的网络 docker inspect 容器I ...

  3. 383 day10缓冲流、转换流、序列化流

    day10[缓冲流.转换流.序列化流] 主要内容 缓冲流 转换流 序列化流 打印流 教学目标 [ ] 能够使用字节缓冲流读取数据到程序 [ ] 能够使用字节缓冲流写出数据到文件 [ ] 能够明确字符缓 ...

  4. js 签字插件

    1.jq-signature  http://bencentra.github.io/jq-signature/    支持的jquery版本低 2.HTML5 canvas   http://www ...

  5. dede5.7 给栏目添加上缩略图

    如我们一个栏目列表都用缩略图来表示,而不仅仅只是文字,如果没有这项功能会非常麻烦,网上有很多这方面的资料,但是都试过了有很多问题,自己研究一下,测试基本通过.新加字段 typeimg后台执行SQL: ...

  6. Orchard Core Framework Samples

    解决方案包含内容 多租户应用 一个ASP.NET Core MVC应用程序,它引用模块项目,并为两个启用了不同模块的租户提供支持. 此Web应用程序的主页提供了更多信息,并链接到两个租户和模块端点.租 ...

  7. jmeter之图形结果

    jmeter监听器中的图形报表如何分析,反正第一次看到图时,不觉明厉害. 样本数目:是总共发送到服务器的请求数. 最新样本:是代表时间的数字,是服务器响应最后一个请求的时间. 吞吐量:是服务器每分钟处 ...

  8. cgroup之cpu关键参数

    cpu.cfs_period_us specifies a period of time in microseconds (µs, represented here as "us" ...

  9. HTML 网页开发、CSS 基础语法——三. HTML概念

    1.纯文本格式 纯文本格式,就是没有任何文本修饰的,没有任何粗体,下划线,斜体,图形,符号,或者特殊字符,及特殊打印格式的文本,只保存文本,不保存其格式设置. ①格式对比 纯文本格式 富文本格式 最常 ...

  10. 如何通过云效Flow完成自动化部署—主机部署

    如何通过云效Flow完成自动化部署-主机部署,云效流水线Flow是持续交付的载体,通过构建自动化.集成自动化.验证自动化.部署自动化,完成从开发到上线过程的持续交付.通过持续向团队提供及时反馈,让交付 ...