找回密码
 注册帐号

扫一扫,访问微社区

碧俐千仞 如果这篇文章说不清epoll的本质,那就过来掐死我吧! (2)

6
回复
1123
查看
[ 复制链接 ]
排名
19948
昨日变化

42

主题

223

帖子

882

积分

Rank: 9Rank: 9Rank: 9

UID
53741
好友
39
蛮牛币
2659
威望
0
注册时间
2014-11-6
在线时间
211 小时
最后登录
2019-8-22

专栏作家

2019-5-25 16:16:47 显示全部楼层 阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 tyxxxx 于 2019-5-25 16:19 编辑

从事服务端开发,少不了要接触网络编程。epoll作为linux下高性能网络服务器的必备技术至关重要,大部分游戏服务器都使用到这一多路复用技术。文章核心思想是:要让读者清晰明白EPOLL为什么性能好。

文/罗培羽


上篇回顾

一、从网卡接收数据说起

二、如何知道接收了数据?

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


四、内核接收网络数据全过程

这一步,贯穿网卡、中断、进程调度的知识,叙述阻塞recv下,内核接收数据全过程。

如下图所示,进程在recv阻塞期间,计算机收到了对端传送的数据(步骤①)。数据经由网卡传送到内存(步骤②),然后网卡通过中断信号通知cpu有数据到达,cpu执行中断程序(步骤③)。此处的中断程序主要有两项功能,先将网络数据写入到对应socket的接收缓冲区里面(步骤④),再唤醒进程A(步骤⑤),重新将进程A放入工作队列中。

a1.jpg

内核接收数据全过程

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

a2.jpg

唤醒进程

以上是内核接收数据全过程

这里留有两个思考题,大家先想一想。

其一,操作系统如何知道网络数据对应于哪个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收到数据,然后做出处理。



  • [AppleScript] 纯文本查看 复制代码
    
    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, ...)){
    
                //fds的数据处理
    
            }
    
        }
    
    }
    


select的流程

select的实现思路很直接。假如程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。

a3.jpg

操作系统把进程A分别加入这三个socket的等待队列中

当任何一个socket收到数据后,中断程序将唤起进程。下图展示了sock2接收到了数据的处理流程。

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

a4.jpg

sock2接收到了数据,中断程序唤起进程A

所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。如下图所示。

a5.jpg

将进程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阻塞进程。显而易见的,效率就能得到提升。

a6.jpg

相比select,epoll拆分了功能

为方便理解后续的内容,我们先复习下epoll的用法。如下的代码中,先用epoll_create创建一个epoll对象epfd,再通过epoll_ctl将需要监视的socket添加到epfd中,最后调用epoll_wait等待数据。

[AppleScript] 纯文本查看 复制代码
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收到数据。

a7.jpg

就绪列表示意图

以下内容待续

七、epoll的原理和流程

八、epoll的实现细节

九、结论


《网络游戏实战(第2版)》是一本专门介绍如何开发多人网络游戏的书籍,用实例介绍开发游戏的全过程,非常实用。书中对网络编程有详细的讲解,全书用一个大例子贯穿,真正的“实战”教程。


回复

使用道具 举报

7日久生情
2132/5000
排名
4092
昨日变化

0

主题

1406

帖子

2132

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
1933
威望
0
注册时间
2017-11-16
在线时间
364 小时
最后登录
2019-8-24
2019-5-25 22:59:02 显示全部楼层
66666666666666666666666666666666666
回复 支持 反对

使用道具 举报

7日久生情
3176/5000
排名
295
昨日变化

0

主题

600

帖子

3176

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
88896
好友
0
蛮牛币
12822
威望
0
注册时间
2015-4-2
在线时间
678 小时
最后登录
2019-8-17
2019-5-26 10:25:51 显示全部楼层
好闻,又复习了一下网络编程
回复 支持 反对

使用道具 举报

7日久生情
2925/5000
排名
2230
昨日变化

1

主题

1898

帖子

2925

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蛮牛币
3264
威望
0
注册时间
2015-8-21
在线时间
396 小时
最后登录
2019-8-23
2019-5-26 23:40:11 显示全部楼层
谢谢楼主大大。
回复

使用道具 举报

6蛮牛粉丝
1160/1500
排名
2066
昨日变化

0

主题

262

帖子

1160

积分

Rank: 6Rank: 6Rank: 6

UID
26073
好友
1
蛮牛币
4413
威望
0
注册时间
2014-5-21
在线时间
230 小时
最后登录
2019-8-23
2019-5-27 08:40:50 显示全部楼层
66666666,多谢知识传播者!
回复 支持 反对

使用道具 举报

5熟悉之中
783/1000
排名
10706
昨日变化

0

主题

523

帖子

783

积分

Rank: 5Rank: 5

UID
301976
好友
1
蛮牛币
1171
威望
0
注册时间
2018-10-31
在线时间
162 小时
最后登录
2019-8-24
2019-6-13 13:53:17 显示全部楼层
服务器大佬,感谢只是分享...
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1290/1500
排名
3155
昨日变化

25

主题

367

帖子

1290

积分

Rank: 6Rank: 6Rank: 6

UID
194101
好友
7
蛮牛币
7492
威望
0
注册时间
2016-12-19
在线时间
470 小时
最后登录
2019-8-22
3 天前 显示全部楼层
我来掐你了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则