0717-7821348
新闻中心

欢乐彩手机版下载

您现在的位置: 首页 > 新闻中心 > 欢乐彩手机版下载
欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!
2019-05-11 22:38:37


文/罗培羽

目录

一、从网卡接纳数据说起

二、怎么知道接纳了数据?

三、进程堵塞为什么不占用cpu资源?

四、内核接纳网络数据全进程

五、一起监督多个socket的简略办法

六、epoll的规划思路

七、epoll的原理和流程

八、epoll的完成细节

九、定论

从事服务端开发,少不了要触摸网络编程。epoll作为linux下高性能网络服务器的必备技能至关重要,nginx、redis、skynet和大部分游戏服务器都运用到这一多路复用技能。

由于epoll的重要性,不少游戏公司在招聘服务端同学时,会问及epoll相关的问题。比方epoll和select的差异是什么?epoll高功率的原因是什么?假定只靠背诵,明显不能算上深化的了解。

网上尽管也有不少解说epoll的文章,但要不是过于粗浅,便是堕入源码解析,很少能有通俗易懂的。所以决议编写此文,让缺少专业布景常识的读者也可以了解epoll的原理。文章中心思维是:

要让读者明晰了解EPOLL为什么性能好。

本文会从网卡接纳数据的流程讲起,串联起CPU中止、操作体系进程调度等常识;再一步步剖析堵塞接纳数据、select到epoll的进化进程;最终探求epoll的完成细节。

一、从网卡接纳数据说起

下图是一个典型的核算机结构图,核算机由CPU、存储器(内存)、网络接口等部件组成。了解epoll实质的第一步,要从硬件的视点看核算机怎样接纳网络数据。


核算机结构图(图片来历:linux内核彻底注释之微型核算机组成结构)


下图展现了网卡接纳数据的进程。在①阶段,网卡收到网线传来的数据;经过②阶段的硬件电路的传输;最终将数据写入到内存中的某个地址上(③阶段)。这个进程触及到DMA传输、IO通路挑选等硬件有关的常识,但咱们只需知道:网卡会把接纳到的数据写入内存


网卡接纳数据的进程


经过硬件传输,网卡接纳的数据寄存到内存中。操作体系就可以去读取它们。

二、怎么知道接纳了数据?

了解epoll实质的第二步,要从CPU的视点来看数据接纳。要了解这个问题,要先了解一个概念——中止。

核算机履行程序时,会有优先级的需求。比方,当核算机收欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!到断电信号时(电容可以保存少数电量,供CPU运转很短的一小段时刻),它应立即去保存数据,保存数据的程序具有较高的优先级。

一般来说,由硬件发作的信号需求cpu立马做出回应(否则数据或许就丢掉),所以它的优先级很高。cpu理应中止掉正在履行的程序,去做出呼应;当cpu完成对硬件的呼应后,再从头履行用户程序。中止的进程如下图,和函数调用差不多。只不过函数调用是事前定好方位,而中止的方位由“信号”决议。


中止程序调用


以键盘为例,当用户按下键盘某个按键时,键盘会给cpu的中止引脚宣布一个高电平。cpu可以捕获这个信号,然后履行键盘中止程序。下图展现了各种硬件经过中止与cpu交互。


cpu中止(图片来历:net.pku.edu.cn)


现在可以答复本节提出的问题了:当网卡把数据写入到内存后,网卡向cpu宣布一个中止信号,操作体系便能得知有新数据到来,再经过网卡中止程序去处理数据。

三、进程堵塞为什么不占用cpu资源?

了解epoll实质的第三步,要从操作体系进程调度的视点来看数据接纳。堵塞是进程调度的要害一环,指的是进程在等候某事情(如接纳到网络数据)发作之前的等候状况,recv、select和epoll都是堵塞办法。了解“进程堵塞为什么不占用cpu资源?”,也就可以了解这一步。

为简略起见,咱们从一般的recv接纳开端剖析,先看看下面代码:

//创立socketint s = socket(AF_INET, SOCK_STREAM, 0); //绑定bind(s, ...)//监听listen(s, ...)//承受客户端衔接int c = accept(s, ...)//接纳客户端数据recv(c, ...);//将数据打印出来printf(...)

这是一段最根底的网络编程代码,先新建socket目标,顺次调用bind、listen、accept,最终调用recv接纳数据。recv是个堵塞办法,当程序运转到recv时,它会一向等候,直到接纳到数据才往下履行。

刺进:假定您还不太熟悉网络编程,欢迎阅览我编写的《Unity3D网络游戏实战(第2版)》,会有具体的介绍。

那么堵塞的原理是什么?

作业行列

操作体系为了支撑多使命,完成了进程调度的功用,会把进程分为“运转”和“等候”等几种状况。运转状况是进程取得cpu运用权,正在履行代码的状况;等候状况是堵塞状况,比方上述程序运转到recv时,程序会从运转状况变为等候状况,接纳到数据后又变回运转状况。操作体系会分时履行各个运转状况的进程,由于速度很快,看上去就像是一起履行多个使命。

下图中的核算机中运转着A、B、C三个进程,其间进程A履行着上述根底网络程序,一开端,这3个进程都被操作体系的作业行列所引证,处于运转状况,会分时履行。


作业行列中有A、B和C三个进程


等候行列

当进程A履行到创立socket的句子时,操作体系会创立一个由文件体系办理的socket目标(如下图)。这个socket目标包含了发送缓冲区、接纳缓冲区、等候行列等成员。等候行列是个十分重要的结构,它指向一切需求等候该socket事情的进程。


创立socket


当程序履行到recv时,操作体系会将进程A从作业行列移动到该socket的等候行列中(如下图)。由于作业行列只剩下了进程B和C,根据进程调度,cpu会轮番履行这两个进程的程序,不会履行进程A的程序。所以进程A被堵塞,不会往下履行代码,也不会占用cpu资源。


socket的等候行列


ps:操作体系增加等候行列仅仅增加了对这个“等候中”进程的引证,以便在接纳到数据时获取进程目标、将其唤醒,而非直接将进程办理归入自己之下。上图为了便利阐明,直接将进程挂到等候行列之下。

唤醒进程

当socket接纳到数据后,操作体系将该socket等候行列上的进程从头放回到作业行列,该进程变成运转状况,持续履行代码。也由于socket的接纳缓冲区现已有了数据,recv可以回来接纳到的数据。

四、内核接纳网络数据全进程

这一步,贯穿网卡、中止、进程调度的常识,叙说堵塞recv下,内核接纳数据全进程。

如下图所示,进程在recv堵塞期间,核算机收到了对端传送的数据(进程①)。数据经由网卡传送到内存(进程②),然后网卡经过中止信号告诉cpu有数据抵达,cpu履行中止程序(进程③)。此处的中止程序主要有两项功用,先将网络数据写入到对应socket的接纳缓冲区里边(进程④),再唤醒进程A(进程⑤),从头将进程A放入作业行列中。


内核接纳数据全进程


唤醒进程的进程如下图所示。


唤醒进程


以上是内核接纳数据全进程

这儿留有两个思考题,咱们先想一想。

其一,操作体系怎么知道网络数据对应于哪个socket?

其二,怎么一起监督多个socket的数据?

(——我是分割线,想好了才干往下看哦~)

发布答案的时刻到了。

第一个问题:由于一个socket对应着一个端口号,而网络数据包中包含了ip和端口的信息,内核可以经过端口号找到对应的socket。当然,为了进步处理速度,操作体系会保护端口号到socket的索引结构,以快速读取。

第二个问题是多路复用的重中之重,是本文后半部分的要点!

五、一起监督多个socket的简略办法

服务端需求办理多个客户端衔接,而recv只能监督单个socket,这种对立下,人们开端寻觅监督多个socket的办法。epoll的要义是高效的监督多个socket。从前史开展视点看,必定先呈现一种不太高效的办法,人们再加以改善。只需先了解了不太高效的办法,才干够了解epoll的实质。

假定可以预先传入一个socket列表,假定列表中的socket都没有数据,挂起进程,直到有一个socket收到数据,唤醒进程。这种办法很直接,也是select的规划思维。

为便利了解,咱们先温习select的用法。在如下的代码中,先预备一个数组(下面代码中的fds),让fds寄存着一切需求监督的socket。然后调用select,假定fds中的一切socket都没有数据,select会堵塞,直到有一个socket接纳到数据,select回来,唤醒进程。用户可以遍历fds,经过FD_ISSET判别具体哪个socket收到数据,然后做出处理。

int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...)listen(s, ...)
int fds[] = 寄存需求监听的socket
while(1){
int n = select(..., fds, ...)
for(int i=0; i < fds.count; i++){
if(FD_ISSET(fds[i], ...)){
//fds[i]的数据处理
}
}}

select的流程

select的完成思路很直接。假定程序一起监督如下图的sock1、sock2和sock3三个socket,那么在调用select之后,欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!操作体系把进程A别离参加这三个socket的等候行列中。


操作体系把进程A别离参加这三个socket的等候行列中


当任何一个socket收到数据后,中止程序将引发进程。下图展现了sock2接纳到了数据的处理流程。

ps:recv和select的中止回调可以设置成不同的内容。

sock2接纳到了数据,中止程序引发进程A


所谓引发进程,便是将进程从一切的等候行欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!列中移除,参加到作业行列里边。如下图所示。


将进程A从一切等候行列中移除,再参加到作业行列里边


经由这些进程,当进程A被唤醒后,它知道至少有一个socket接纳了数据。程序只需遍历一遍socket列表,就可以得到安排妥当的socket。

这种简略办法卓有成效,在简直一切操作体系都有对应的完成。

可是简略的办法往往有缺点,主要是:

其一,每次调用select都需求将进程参加到一切监督socket的等候行列,每次唤醒都需求从每个行列中移除。这儿触及了两次遍历,并且每次都要将整个fds列表传递给内核,有必定的开支。正是由于遍历操作开支大,出于功率的考量,才会规则select的最大监督数量,默许只能监督1024个socket。

其二,进程被唤醒后,程序并不知道哪些socket收到数据,还需求遍历一次。

那么,有没有削减遍历的办法?有没有保存安排妥当socket的办法?这两个问题便是epoll技能要处理的。

弥补阐明:本节只解说了select的一种景象。当程序调用select时,内核会先遍历一遍socket,假定有一个以上的socket接纳缓冲区有数据,那么select直接回来,不会堵塞。这也是为什么select的回来值有或许大于1的原因之一。假定没有socket有数据,进程才会堵塞。

六、epoll的规划思路

epoll是在select呈现N多年后才被创造的,是select和poll的增强版别。epoll经过以下一些办法来改善功率。

办法一:功用别离

select低效的原因之一是将“保护等候行列”和“堵塞进程”两个进程合二为一。如下图所示,每次调用select都需求这两步操作,但是大多数运用场景中,需求监督的socket相对固定,并不需求每次都修正。epoll将这两个操作分隔,先用epoll_ctl保护等候行列,再调用epoll_wait堵塞进程欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!。清楚明了的,功率就能得到提高。


比较select,epoll拆分了功用


为便利了解后续的内容,咱们先温习下epoll的用法。如下的代码中,先用epoll_create创立一个epoll目标epfd,再经过epoll_ctl将需求监督的socket增加到epfd中,最终调用epoll_wait等候数据。

int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...)listen(s, ...)
int epfd = epoll_create(...);epoll_ctl(epfd, ...); //将一切需求监听的socket增加到epfd中
while(1){
int n = epoll_wait(...)
for(接纳到数据的socket){
//处理
}}

功用别离,使得epoll有了优化的或许。

办法二:安排妥当列表

select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。假定内核保护一个“安排妥当列表”,引证收到数据的socket,就能防止遍历。如下图所示,核算机共有三个socket,收到数据的sock2和sock3被rdlist(安排妥当列表)所引证。当进程被唤醒后,只需获取rdlist的内容,就可以知道哪些socket收到数据。


安排妥当列表示意图


七、epoll的原理和流程

本节会以示例和图表来解说epoll的原理和流程。

创立epoll目标

如下图所示,当某个进程调用epoll_create办法时,内核会创立一个eventpoll目标(也便是程序中epfd所代表的目标)。eventpoll目标也是文件体系中的一员,和socket相同,它也会有等候行列。


内核创立eventpoll目标


创立一个代表该epoll的eventpoll目标是有必要的,由于内核要保护“安排妥当列表”等数据,“安排妥当列表”可以作为eventpoll的成员。

保护监督列表

创立epoll目标后,可以用epoll_ctl增加或删去所要监听的socket。以增加socket为例,如下图,假定经过epoll_ctl增加sock1、sock2和sock3的监督,内核会将eventpoll增加到这三个socket的等候行列中。


增加所要监听的socket


当socket收到数据后,中止程序会操作eventpoll目标,而不是直接操作进程。

接纳数据

当socket收到数据后,中止程序会给eventpoll的“安排妥当列表”增加socket引证。如下图展现的是sock2和sock3收到数据后,中止程序让rdlist引证这两个socket。


给安排妥当列表增加引证


eventpoll目标适当所以socket和进程之间的中介,socket的数据接纳并不直接影响进程,而是经过改动eventpoll的安排妥当列表来改动进程状况。

当程序履行到epoll_wait时,假定rdlist现已引证了socket,那么epoll_wait直接回来,假定rdlist为空,堵塞进程。

堵塞和唤醒进程

假规划算机中正在运转进程A和进程B,在某时刻进程A运转到了epoll_wait句子。如下图所示,内核会将进程A放入eventpoll的等候行列中,堵塞进程。


epoll_wait堵塞进程


当socket接纳到数据,中止程序一方面修正rdlist,另一方面唤醒eventpoll等候行列中的进程,进程A再次进入运转状况(如下图)。也由于rdlist的存在,进程A可以知道哪些socket发作了改变。

epoll唤醒进程


八、epoll的完成细节

至此,信任读者对epoll的实质现已有必定的了解。但咱们还留有一个问题,eventpoll的数据结构是什么姿态?

再留两个问题,安排妥当行列应该应运用什么数据结构?eventpoll应运用什么数据结构来办理经过epoll_ctl增加或删去的socket?


(——我是分割线,想好了才干往下看哦~)

如下图所示,eventpoll包含了lock、mtx、wq(等候行列)、rdlist等成员。rdlist和rbr是咱们所关怀的。


epoll原理示意图,图片来历:《深化了解Nginx:模块开发与架构解析(第二版)》,陶辉


安排妥当列表的数据结构

安排妥当列表引证着安排妥当的socket,所以它应可以快速的刺进数据。

程序或许随时调用epoll_ctl增加监督socket,也或许随时删去。当删去时,若该socket现已寄存在安排妥当列表中,它欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!也应该被移除。

所以安排妥当列表应是一种可以快速刺进和删去的数据结构。双向链表便是这样一种数据结构,epoll运用双向链表来完成安排妥当行列(对应上图的rdllist)。

索引结构

已然epoll将“保护监督行列”和“进程堵塞”别离,也意味着需求有个数据结构来保存监督的socket。至少要便利的增加和移除,还要便于查找,以防止重复增加。红黑树是一种自平衡二叉查找树,查找、刺进和删去时刻复杂度都是O(欢乐彩票首页-游戏编程干货! 一篇文章说清epoll的实质!log(N)),功率较好。epoll运用了红黑树作为索引结构(对应上图的rbr)。

ps:由于操作体系要统筹多种功用,以及由更多需求保存的数据,rdlist并非直接引证soc女人和猪ket,而是经过epitem直接引证,红黑树的节点也是epitem目标。相同,文件体系也并非直接引证着socket。为便利了解,本文中省掉了一些直接结构。

九、定论

epoll在select和poll(poll和select根本相同,有少数改善)的根底引入了eventpoll作为中间层,运用了先进的数据结构,是一种高效的多路复用技能。

再留一点作业!

下表是个很常见的表,描绘了select、poll和epoll的差异。读完本文,读者能否解说select和epoll的时刻复杂度为什么是O(n)和O(1)?


select、poll和epoll的差异。图片来历《Linux高性能服务器编程》


已然提到网络编程,笔者的《Unity3D网络游戏实战(第2版)》是一本专门介绍怎么开发多人网络游戏的书本,用实例介绍开发游戏的全进程,十分有用,全书用一个大比如贯穿,真实的“实战”教程。我还在规划一本游戏服务端的书本,c++和lua方向,期望可以做到深化浅出、有用、有用。书中对网络编程有具体的解说,为了高质量,或许会在很长一段时刻后才开端写,欢迎给我些主张(aglab#foxmail点com)。

称谢:本文力求具体阐明epoll的原理,特别感谢 ljhsaka、AllenKong12、雄爷、堂叔 等搭档审理了文章并给予修正意见。