[apue] epoll 的一些不为人所注意的特性
之前曾经使用 epoll 构建过一个轻量级的 tcp 服务框架:
一个工业级、跨平台、轻量级的 tcp 网络服务框架:gevent
在调试的过程中,发现一些 epoll 之前没怎么注意到的特性。
a) iocp 是完全线程安全的,即同时可以有多个线程等待在 iocp 的完成队列上;
而 epoll 不行,同时只能有一个线程执行 epoll_wait 操作,因此这里需要做一点处理,
网上有人使用 condition_variable + mutex 实现 leader-follower 线程模型,但我只用了一个 mutex 就实现了,
当有事件发生了,leader 线程在执行事件处理器之前 unlock 这个 mutex,
就可以允许等待在这个 mutex 上的其它线程中的一个进入 epoll_wait 从而担任新的 leader。
(不知道多加一个 cv 有什么用,有明白原理的提示一下哈)
b) epoll 在加入、删除句柄时是可以跨线程的,而且这一操作是线程安全的。
之前一直以为 epoll 会像 select 一像,添加或删除一个句柄需要先通知 leader 从 epoll_wait 中醒来,
在重新 wait 之前通过 epoll_ctl 添加或删除对应的句柄。但是现在看完全可以在另一个线程中执行 epoll_ctl 操作
而不用担心多线程问题。这个在 man 手册页也有描述(man epoll_wait):
NOTES
While one thread is blocked in a call to epoll_pwait(), it is possible for another thread to
add a file descriptor to the waited-upon epoll instance. If the new file descriptor becomes
ready, it will cause the epoll_wait() call to unblock. For a discussion of what may happen if a file descriptor in an epoll instance being monitored
by epoll_wait() is closed in another thread, see select(2).
c) epoll 有两种事件触发方式,一种是默认的水平触发(LT)模式,即只要有可读的数据,就一直触发读事件;
还有一种是边缘触发(ET)模式,即只在没有数据到有数据之间触发一次,如果一次没有读完全部数据,
则也不会再次触发,除非所有数据被读完,且又有新的数据到来,才触发。使用 ET 模式的好处是,
不用在每次执行处理器前将句柄从 epoll 移除、在执行完之后再加入 epoll 中,
(如果不这样做的话,下一个进来的 leader 线程还会认为这个句柄可读,从而导致一个连接的数据被多个线程同时处理)
从而导致频繁的移除、添加句柄。好多网上的 epoll 例子也推荐这种方式。但是我在亲自验证后,发现使用 ET 模式有两个问题:
1)如果连接上来了大量数据,而每次只能读取部分(缓存区限制),则第 N 次读取的数据与第 N+1 次读取的数据,
有可能是两个线程中执行的,在读取时它们的顺序是可以保证的,但是当它们通知给用户时,第 N+1 次读取的数据
有可能在第 N 次读取的数据之前送达给应用层。这是因为线程的调度导致的,虽然第 N+1 次数据只有在第 N 次数据
读取完之后才可能产生,但是当第 N+1 次数据所在的线程可能先于第 N 次数据所在的线程被调度,上述场景就会产生。
这需要细心的设计读数据到给用户之间的流程,防止线程抢占(需要加一些保证顺序的锁);
2)当大量数据发送结束时,连接中断的通知(on_error)可能早于某些数据(on_read)到达,其实这个原理与上面类似,
就是客户端在所有数据发送完成后主动断开连接,而获取连接中断的线程可能先于末尾几个数据所在的线程被调度,
从而在应用层造成混乱(on_error 一般会删除事件处理器,但是 on_read 又需要它去做回调,好的情况会造成一些
数据丢失,不好的情况下直接崩溃)
鉴于以上两点,最后我还是使用了默认的 LT 触发模式,幸好有 b) 特性,我仅仅是增加了一些移除、添加的代码,
而且我不用在应用层加锁来保证数据的顺序性了。
d) 一定要捕捉 SIGPIPE 事件,因为当某些连接已经被客户端断开时,而服务端还在该连接上 send 应答包时:
第一次 send 会返回 ECONNRESET(104),再 send 会直接导致进程退出。如果捕捉该信号后,则第二次 send 会返回 EPIPE(32)。
这样可以避免一些莫名其妙的退出问题(我也是通过 gdb 挂上进程才发现是这个信号导致的)。
e) 当管理多个连接时,通常使用一种 map 结构来管理 socket 与其对应的数据结构(特别是回调对象:handler)。
但是不要使用 socket 句柄作为这个映射的 key,因为当一个连接中断而又有一个新的连接到来时,linux 上倾向于用最小的
fd 值为新的 socket 分配句柄,大部分情况下,它就是你刚刚 close 或客户端中断的句柄。这样一来很容易导致一些混乱的情况。
例如新的句柄插入失败(因为旧的虽然已经关闭但是还未来得及从 map 中移除)、旧句柄的清理工作无意间关闭了刚刚分配的
新连接(清理时 close 同样的 fd 导致新分配的连接中断)……而在 win32 上不存在这样的情况,这并不是因为 winsock 比 bsdsock 做的更好,
相同的, winsock 也存在新分配的句柄与之前刚关闭的句柄一样的场景(当大量客户端不停中断重连时);而是因为 iocp 基于提前
分配的内存块作为某个 IO 事件或连接的依据,而 map 的 key 大多也依据这些内存地址构建,所以一般不存在重复的情况(只要还在 map 中就不释放对应内存)。
经过观察,我发现在 linux 上,即使新的连接占据了旧的句柄值,它的端口往往也是不同的,所以这里使用了一个三元组作为 map 的 key:
{ fd, local_port, remote_port }
当 fd 相同时,local_port 与 remote_port 中至少有一个是不同的,从而可以区分新旧连接。
f) 如果连接中断或被对端主动关闭连接时,本端的 epoll 是可以检测到连接断开的,但是如果是自己 close 掉了 socket 句柄,则 epoll 检测不到连接已断开。
这个会导致客户端在不停断开重连过程中积累大量的未释放对象,时间长了有可能导致资源不足从而崩溃。
目前还没有找到产生这种现象的原因,Windows 上没有这种情况,有清楚这个现象原因的同学,不吝赐教啊
[apue] epoll 的一些不为人所注意的特性的更多相关文章
- c++ 跨平台线程同步对象那些事儿——基于 ace
前言 ACE (Adaptive Communication Environment) 是早年间很火的一个 c++ 开源通讯框架,当时 c++ 的库比较少,以至于谈 c++ 网络通讯就绕不开 ACE, ...
- ASP.NET 整理比较全的URL重写解决方案
经常有人请我指导应该如何动态地“重写”URL,以在他们的ASP.NETweb应用中发布比较干净的URL端点.这个博客帖子概述了几个方法,你可以用来在ASP.NET中干净地映射或重写URL,以及按照你自 ...
- (十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- epoll和select区别
先说下本文框架,先是问题引出,然后概括两个机制的区别和联系,最后介绍每个接口的用法 一.问题引出 联系区别 问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一 ...
- Select与Epoll比较
一.问题引出 联系区别 问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解 ...
- Socket网络编程--epoll小结
以前使用的用于I/O多路复用为了方便就使用select函数,但select这个函数是有缺陷的.因为它所支持的并发连接数是有限的(一般小于1024),因为用户处理的数组是使用硬编码的.这个最大值为FD_ ...
- I/O多路转接-epoll
By francis_hao Aug 5,2017 APUE讲多路转接的章节介绍了select.pselect和poll函数.而epoll是linux内核在2.5.44引入的.在glibc ...
- [APUE]进程控制(上)
一.进程标识 进程ID 0是调度进程,常常被称为交换进程(swapper).该进程并不执行任何磁盘上的程序--它是内核的一部分,因此也被称为系统进程.进程ID 1是init进程,在自举(bootstr ...
- [APUE]UNIX进程的环境(下)
一.共享库 共享库使得可执行文件中不再需要包含常用的库函数,而只需在所有进程都可存取的存储区中保存这种库例程的一个副本.程序第一次执行的时候或第一次调用某个库函数的时候,用动态链接方法将程序与共享库函 ...
随机推荐
- 【WEB自动化】【第一节】【Xpath和CSS元素定位】
目前自动化测试开始投入WEB测试,使用RF及其selenium库,模拟对WEB页面进行操作,此过程中首先面对的问题就是对WEB页面元素的定位,几乎所有的关键字都需要传入特定的WEB页面元素,因此掌握常 ...
- 解决 es CircuitBreakingException 问题
比如频繁报如下错误, [2019-06-16T15:31:22,778][DEBUG][o.e.a.a.c.n.i.TransportNodesInfoAction] [node-xxx] faile ...
- hdu5687 字典树
hdu5687 #include <bits/stdc++.h> using namespace std; , n = ; struct node { ]; int cnt; void i ...
- HTML5新特性--svg-echarts(重点)-拖动API-WebWorker
一.html5新特性--svg--(折线/渐变特效对象/滤镜) #折线:多个坐标点组件一条折线 <polyline points="50,50 70,55 60,66 " s ...
- 7.2 Go type assertion
7.2 Go type assertion 类型断言是使用在接口值上的操作. 语法x.(T)被称为类型断言,x代表接口的类型,T代表一个类型检查. 类型断言检查它操作对象的动态类型是否和断言类型匹配. ...
- ABAP基础4:模块化
子程序定义 以form开始,以endform结束,用perform语句调用,from语句可以在程序内部/外部,perform一定要写在前面 perform. from. 子程序模块 endform. ...
- 王艳 201771010127《面向对象程序设计(java)》第八周学习总结
一:理论部分. 1.接口:Java为了克服单继承的缺点,Java使用了接口,一个类可以实现一个或多接口.(接口不是类,而是对类的一组需求描述,它由常量和一组抽象方法组成) 1)通常,接口名称以able ...
- 【python爬虫】scrapy入门2--自定义item
items.py class LianhezaobaospyderItem(scrapy.Item): # define the fields for your item here like: # n ...
- UIAutomator2安装及连接
记录一下自己的偿试过程,内容来自:https://github.com/openatx/uiautomator2 d.service("uiautomator").stop()是因 ...
- JVM调优总结(四)-分代垃圾回收详述
为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对象, ...