【网络IO】(二)多路复用器——内核级介绍

本文详细介绍了网络IO中的多路复用器,包括其原理和与NIO的对比。重点讨论了select/poll的局限性和epoll的优化,如epoll的红黑树和链表数据结构。通过Java代码展示了多路复用器的实践应用,指出无论哪种多路复用器,都是同步非阻塞的网络IO模型。最后预告了多路复用器在多线程环境的应用。
摘要由CSDN通过智能技术生成

前言

这个系列的上一篇文章中,我们介绍过网络IO中的两种IO模型——BIO与NIO,但这两种模型各有缺陷。

BIO将系统资源浪费在了线程调度上,而NIO将系统资源浪费在了用户态与内核态的切换上。

为了解决上面这两种IO模型的缺点,又提出了多路复用器的概念。

多路复用器,根据工作模式的不同,分为select/poll和epoll。

多路复用器的原理

要聊多路复用器,首先要明白“路”是指什么。

我们先来看一张图:

在这里插入图片描述

这张图左边是NIO的工作模式,右边是多路复用器的工作模式。

而其中的“路”就是IO通道,是这个用户程序需要监听的所有socket。

无论是NIO还是多路复用器,都需要知道在监听的所有socket中,有状态更新的是哪些,从而对这些socket进行处理。

而NIO与多路复用器的区别在于如何知道那些有状态更新的sockets。

与NIO的对比

在上一篇文章中我们知道,用户程序是通过fd(file descriptor)与sockets打交道的,也就是说,fd是socket在用户程序中的抽象。

在NIO中,要想知道哪些socket有更新,就需要用户程序对每个socket进行状态检查,注意,是从用户程序中对每个socket进行检查。

这样的检查方式有两个缺点:

  1. 对每一个socket的检查都要单独调用一次系统函数。也就是说,如果应用程序有N个正在监听的socket,就会调用N次系统函数。
  2. 对所有监听的socket都进行了检查,但是真正有状态的更新可能很少的几个,资源被浪费在无效的检查上。

而多路复用器的出现就是为了解决第一个问题,多路复用器只需要一次系统调用,就能够知道有状态更新的socket是哪些。

select/poll

selectpoll是同一类型的多路复用器,区别仅在于能够监听的fd上限。

下面我们以select为例,讲解一下这种多路复用器的工作模式。

要使用select多路复用器,通过系统函数select(fds)即可。

该函数的参数就是一个fd的集合,函数的返回值是参数中有状态更新的fd集合。

所以如果应用程序所监听的socket中,有多少有状态更新,只需要调用一次系统调用,将遍历工作委托给内核。

内核在自己内部遍历所有已经注册的socket,然后将有状态更新的fds一块儿返回给应用程序。

也就是说,在应用程序的角度看,通过多路复用器,只需要O(1)的时间复杂度就能知道有哪些socket有状态更新。

而同样的事情,在NIO模型中需要O(N)的时间复杂度,其中N是所监听的所有socket数。

但这种多路复用器有它们自己的问题。

select/poll的问题

这种多路复用器主要有两个问题:

  1. 每次进行系统调用的时候,都要将所有正在监听的socket的fd传递给系统函数,用户态与内核态之间数据传输量会很大。
  2. 每次还是要全量遍历所有正在监听的socket,仍然有很多无用的系统消耗。

为了解决这两个问题,有了下面这种多路复用器。

epoll

既然已经知道上面的那种多路复用器存在的问题,那么我们来想想解决思路是什么。

既不想给内核传递需要检查的socket名单,又想让内核不遍历所有的socket,还想让内核把有状态更新的socket返回用户程序。

那么解决方案就是——让内核自己维护两个容器,一个装着正在监听的所有的socket,一个装着有状态更新的socket。

这正是epoll的工作原理!

知识储备

在我们详细介绍epoll之前,需要讲一些计算机原理方面的知识。

先提一个问题,当有数据到达网卡后,发生了什么?

答案是,当数据到达网卡后,网卡会向CPU发出IO中断。

什么是中断?中断是一个打断当前CPU的机制,中断中有一个中断函数

CPU读到中断指令后,会暂时中断当前正在进行的任务,转而执行中断函数。

所以网卡中断CPU后,会让CPU来网卡里取出数据,并将数据放在内存中与这个客户端对应的buffer中。

epoll的底层实现

上面我们提到了,当网卡接收到数据后,在CPU的协调下,将数据存放在该客户端所对应的socket中的buffer中。

而epoll在这个中断函数中新添加了一个操作,将数据存放进内存之后,还有后续的操作。

就是将这个被更新socket放在某个容器中,这样应用程序就可以直接通过这个容器知道哪些socket有状态更新了。

并且epoll相对于select/poll,在底层实现中多了两个新东西——一个红黑树还有一个链表。

这两个容器,都存在了进程的共享空间中。

这个红黑树就是来存放要监听的所有socket的,

这个链表就是用来存放有状态更新的socket的,也就是在IO中断后,有状态更新的socket会被放进这个容器中。

epoll的系统调用

不同于select和poll只有一个系统调用,epoll的调用有三个。

分别是

epoll_create:在内核中开辟一个空间来放置红黑树,会返回一个fd,代表着红黑树的内存区域。

epoll_ctl:将要监听的socket以及它的事件注册进红黑树中。

epoll_wait:将有状态更新的socket返回给应用程序,这个系统调用相当于select。

介绍完两种多路复用器的基本原理,我们来看看如何在代码中应用。

多路复用器的实践代码(java)

虽然在操作系统的角度来看,多路复用器分成两类

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值