通常情况下,服务端调用 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. docker-compose 的使用和负载均衡的初探

    docker-compose 的使用和负载均衡的初探 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器 c. 上一篇:Docker 私有仓库 1. ...

  2. C++快读讲解

    C++快读讲解 inline int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') ...

  3. UVA 11853 Paintball(几何数学+DFS)

    https://vjudge.net/problem/UVA-11853 根据题意描述,相当于在一个正方形中有若干个圆形障碍物,问是否能从左边界走到右边界.判断是否有解需要一点创造性的思维:不妨把正方 ...

  4. NOIP初赛:完善程序做题技巧

    最近写的文章好像还很多的.那么今天我们来讨论NOIP初赛的题型--完善程序.完善程序相对是比较难的题目了.全卷100分,完善程序占了大概26分,占比非常大.如果和英语考试试卷做比较,相当于首字母填空( ...

  5. Jenkins 进阶篇 - 任务关联

    有时候我们的一个任务里面会进行很多的步骤,例如构建一个后端的 Java 服务,可能会有代码静态扫描,静态扫描通过后会打包成 jar 或者 war 文件,打包成功后可能还会对制品进行存档备份,然后可能会 ...

  6. springboot多数据源配合docker部署mysql主从实现读写分离

    本篇主要有两部分: 1.使用docker部署mysql主从 实现主从复制 2.springboot项目多数据源配置,实现读写分离 一.使用docker部署mysql主从 实现主从复制 此次使用的是wi ...

  7. Java基础系列(21)- dowhile循环

    do-while循环 对于while语句而言,如果不满足条件,则不能进入循环.但有时候我们需要即使不满足条件,也至少执行一次 do-while循环和while循环相似,不同的是,do-while循环至 ...

  8. 深入浅出WPF-12.绘图与动画

    绘图 1)Brush(画刷) SolidColorBrush实心画刷,直接使用颜色赋值 LinearGradientBrush线性渐变画刷,色彩沿设定的直线方向.按设定的变化点进行渐变 RadialG ...

  9. Linux Bash命令杂记(cut sort uniq wc tee)

    Linux Bash命令杂记(cut sort uniq wc tee) 数据流重定向 标准输入(stdin):代码为0,使用<或<<: 标准输出(stdout):代码为1,使用&g ...

  10. iOS实现XMPP通讯(二)XMPP编程

    项目概述 这是一个可以登录jabber账号,获取好友列表,并且能与好友进行聊天的项目. 使用的是第三方库XMPPFramework框架来实现XMPP通讯. 项目地址:XMPP-Project 项目准备 ...