什么是IO多路复用,理解IO多路复用

1、什么是socket?

咱们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,无论socket,仍是FIFO、管道、终端,对咱们来讲,一切都是文件,一切都是流。在信息 交换的过程当中,咱们都是对这些流进行数据的收发操做,简称为I/O操做(input and output),往流中读出数据,系统调用read,写入数据,系统调用write。不过话说回来了 ,计算机里有这么多的流,我怎么知道要操做哪一个流呢?对,就是文件描述符,即一般所说的fd,一个fd就是一个整数,因此,对这个整数的操做,就是对这个文件(流)的操做。咱们建立一个socket,经过系统调用会返回一个文件描述符,那么剩下对socket的操做就会转化为对这个描述符的操做。不能不说这又是一种分层和抽象的思想vim

2、阻塞?

什么是程序的阻塞呢?想象这种情形,好比你等快递,但快递一直没来,你会怎么作?有两种方式:网络

  • 快递没来,我能够先去睡觉,而后快递来了给我打电话叫我去取就好了。
  • 快递没来,我就不停的给快递打电话说:擦,怎么还没来,给老子快点,直到快递来。

很显然,你没法忍受第二种方式,不只耽搁本身的时间,也会让快递很想打你。
而在计算机世界,这两种情形就对应阻塞和非阻塞忙轮询。socket

  • 非阻塞忙轮询:数据没来,进程就不停的去检测数据,直到数据来。
  • 阻塞:数据没来,啥都不作,直到数据来了,才进行下一步的处理。

先说说阻塞,由于一个线程只能处理一个套接字的I/O事件,若是想同时处理多个,能够利用非阻塞忙轮询的方式,伪代码以下:函数

while true
{
    for i in stream[]
    {
        if i has data
        read until unavailable
    }
}

咱们只要把全部流从头至尾查询一遍,就能够处理多个流了,但这样作很很差,由于若是全部的流都没有I/O事件,白白浪费CPU时间片。正若有一位科学家所说, 计算机全部的问题均可以增长一个中间层来解决,一样,为了不这里cpu的空转,咱们不让这个线程亲自去检查流中是否有事件,而是引进了一个代理(一开始是select,后来是poll),这个代理很牛,它能够同时观察许多流的I/O事件,若是没有事件,代理就阻塞,线程就不会挨个挨个去轮询了,伪代码以下:

while true
{
    select(streams[]) //这一步死在这里,知道有一个流有I/O事件时,才往下执行
    for i in streams[]
    {
        if i has data
        read until unavailable
    }
}

可是依然有个问题,咱们从select那里仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至所有),咱们只能无差异轮询全部流,找出能读出数据,或者写入数据的流,对他们进行操做。因此select具备O(n)的无差异轮询复杂度,同时处理的流越多,无差异轮询时间就越长。spa

epoll能够理解为event poll,不一样于忙轮询和无差异轮询,epoll会把哪一个流发生了怎样的I/O事件通知咱们。因此咱们说epoll其实是事件驱动(每一个事件关联上fd)的,此时咱们对这些流的操做都是有意义的。(复杂度下降到了O(1))伪代码以下:线程

while true
{
    active_stream[] = epoll_wait(epollfd)
    for i in active_stream[]
    {
        read or write till
    }
}

能够看到,select和epoll最大的区别就是:select只是告诉你必定数目的流有事件了,至于哪一个流有事件,还得你一个一个地去轮询,而epoll会把发生的事件告诉你,经过发生的事件,就天然而然定位到哪一个流了。不能不说epoll跟select相比,是质的飞跃,我以为这也是一种牺牲空间,换取时间的思想,毕竟如今硬件愈来愈便宜了。代理

3、I/O多路复用

好了,咱们讲了这么多,再来总结一下,到底什么是I/O多路复用。
先讲一下I/O模型:
首先,输入操做通常包含两个步骤:unix

  1. 等待数据准备好(waiting for data to be ready)。对于一个套接口上的操做,这一步骤关系到数据从网络到达,并将其复制到内核的某个缓冲区。
  2. 将数据从内核缓冲区复制到进程缓冲区(copying the data from the kernel to the process)。

其次了解一下经常使用的3种I/O模型:code

一、阻塞I/O模型

最普遍的模型是阻塞I/O模型,默认状况下,全部套接口都是阻塞的。 进程调用recvfrom系统调用,整个过程是阻塞的,直到数据复制到进程缓冲区时才返回(固然,系统调用被中断也会返回)。接口

1

二、非阻塞I/O模型

当咱们把一个套接口设置为非阻塞时,就是在告诉内核,当请求的I/O操做没法完成时,不要将进程睡眠,而是返回一个错误。当数据没有准备好时,内核当即返回EWOULDBLOCK错误,第四次调用系统调用时,数据已经存在,这时将数据复制到进程缓冲区中。这其中有一个操做时轮询(polling)。

2

三、I/O复用模型

此模型用到select和poll函数,这两个函数也会使进程阻塞,select先阻塞,有活动套接字才返回,可是和阻塞I/O不一样的是,这两个函数能够同时阻塞多个I/O操做,并且能够同时对多个读操做,多个写操做的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视全部select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,咱们就能够调用recvfrom处理数据。
正由于阻塞I/O只能阻塞一个I/O操做,而I/O复用模型可以阻塞多个I/O操做,因此才叫作多路复用

3