请你谈谈IO多路复用技术

1Socket是什么?

socket是在应用层和传输层中间的抽象层,它把传输层(TCP/UDP)的复杂操作抽象成一些简单的接口,供应用层调用实现进程在网络中的通信。Socket起源于UNIX,在Unix一切皆文件的思想下,进程间通信就被冠名为文件描述符(file desciptor),Socket是一种“打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
在这里插入图片描述
一次完整的网络通信会经过多层的传输:需要经过物理链路层的网线和网卡,网络传输层的IP协议,经过这两层之后网络数据通过Ip地址可以知道传输到那台计算机了,传输到目标计算机后,操作系统内核通过网卡读取网络数据,将网络数据存储在内存中。计算机中会运行不同的网络程序,他们可能对应于系统中的不同进程,那要如何把网卡中的网络数据识别出来是给哪个进程的,要如何持续和稳定地给到对应的应用进程呢?我想这也就是socket设计的目的和想要解决的问题了,提供一些API接口来实现应用层通过操作系统内核读取网卡数据,并且将网络数据正确地分发到对应的应用层程序

2 操作系统的IO操作

为了保护操作系统的安全,会将内存分为用户空间内核空间两个部分。如果用户想要操作内核空间的数据,则需要把数据从内核空间拷贝到用户空间。

举个栗子,如果服务器收到了从客户端过来的请求,并且想要进行处理,那么需要经过这几个步骤:

1 服务器的网络驱动接收到消息之后,向内核申请空间,并在收到完整的数据包(这个过程会产生延时,因为有可能是通过分组传送过来的)后,将其复制到内核空间;
2 数据从内核空间拷贝到用户空间;
3 用户程序进行处理。

在这里插入图片描述
在此以Linux操作系统为例。Linux是一个将所有的外部设备都看作是文件来操作的操作系统,在它看来:everything is a file,那么我们就把对于外部设备的操作都看作是对文件进行操作。而且我们对一个文件的读写,都需要调用 内核提供的 系统调用。

而在Linux中,一个基本的IO会涉及到两个系统对象:一个是调用这个IO的进程对象(用户进程),另一个是系统内核。也就是说,当一个read操作发生时,将会经历这些阶段:

1 通过read系统调用,向内核发送读请求;
2 内核向硬件发送读指令,并等待读就绪;
3 DMA把将要读取的数据复制到指定的内核缓存区中;
4 内核将数据从内核缓存区拷贝到用户进程空间中。

在这里插入图片描述
在此期间会发生几种IO操作:

同步IO: 当用户线程发出IO请求操作后,内核会去查看要读取的数据是否就绪,如果没有,用户线程就一直等待。期间用户线程会不断地轮询数据是否就绪,当数据就绪时,用户线程再把数据从内核拷贝到用户空间。

异步IO: 用户线程只需发出IO请求和接收IO操作完成通知,期间的IO操作由内核自动完成,并发送通知告知用户线程IO操作已经完成。也就是说:在异步IO中,内核线程将数据从内核态拷贝到用户态,并不会对用户线程产生任何阻塞。
在这里插入图片描述

在这里插入图片描述
阻塞和非阻塞操作是针对发起的IO请求操作后: 是否立刻返回一个标志信息而不让请求线程等待,当数据准备未完成时,请求线程的状态:
1 阻塞IO: 当用户线程发起一个IO请求操作,而内核要操作的数据还没就绪,则当前线程被挂起,阻塞等待结果返回。
2 非阻塞IO: 如果数据没有就绪,就会返回一个标志信息告知用户线程:当前的数据还没有就绪。当前用户线程在获得此次请求结果的过程中,还可以做点其他事情。
在这里插入图片描述
在这里插入图片描述

3 NIO与BIO、AIO的区别总结

BIO:是同步阻塞的IO模型,BIO面向字节流或字符流,以流的方式顺序地处理一个或多个字节。从系统调用recv—>将数据从内核复制到用户空间并返回,在这段时间内进程始终阻塞。

 recv函数原型:int recv( SOCKET s, char *buf, int len, int flags)
 功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
 参数一:指定接收端套接字描述符;
 参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
 参数三:指明buf的长度;

在这里插入图片描述
在这里插入图片描述采用BIO形式的网络通信,服务器端启动一个ServerSocket,客户端需要启动Socket来进行一对一连接,如果客户端有多个socket请求,当其中一个socket线程连接成功后,其他的将会阻塞。如果需要实现服务端同时处理多个socket线程,必然需要服务器端开启多serverSocket线程与之对应,这样就会导致如果客户端请求过多,服务器线程开辟过多导致系统崩溃。

NIO:同步非阻塞:用户程序发起IO操作请求后不等待数据,而是会立即返回一个标志信息告知条件不满足,数据未准备好,用户请求程序继续执行其他任务。执行完其他任务,用户程序会主动轮询查看IO操作条件是否满足,如果满足,则用户程序亲自参与拷贝数据动作,这是同步的过程。
在这里插入图片描述

它提供了Buffer,Channel,Selector三大组件。

NIO支持面向Buffer,基于Channel的IO操作,任何数据在Buffer中进行处理,且能够任意改变操作位置,处理灵活。
NIO的IO操作可以是非阻塞的:当一个线程执行从Channel读取数据的IO操作时:如果有数据,则返回数据;如果没数据,不需要阻塞,而是可以直接返回:数据未准备好的通知。

NIO实现非阻塞IO的其中关键组件之一就是Selector,可以注册多个Channel到一个Selector中。Selector可以不断执行select操作,判断这些注册的Channel是否有已就绪的IO事件。一个线程通过使用一个Selector管理多个Channel。
在这里插入图片描述
NIO就是一个线程负责所有请求连接但不处理IO操作,该线程只负责把连接注册到多路复用器selector上,selector多用复用器select轮询到连接有IO请求时候,再启动其它线程处理IO请求操作,实现一个线程或少量线程就可以对应众多的客户端线程。

AIO:异步非阻塞IO模型。异步指的就是数据拷贝阶段完全由操作系统处理,而应用程序只需要等待通知即可。

总结 BIO NIO AIO

同步与异步的区别:在于数据拷贝阶段是否需要完全由操作系统处理。
阻塞和非阻塞操作:是针对发起的IO请求操作后是否立刻返回一个标志信息而不让请求线程等待。
BIO是同步阻塞式的IO模型,面向流操作,保证顺序,如JDK1.4之前的传统IO操作。
NIO是同步非阻塞式的IO模型,面向缓冲区,提供Channel,Buffer,Selector等抽象,如JDK1.4引入的IO操作。
AIO是异步非阻塞式的IO模型,如JDK1.7引入的IO操作

4 什么是IO多路复用

在linux系统中,实际上所有的I/O设备都被抽象为了文件这个概念,一切皆文件,Everything isFile,磁盘、网络数据、终端,甚至进程间通信工具管道pipe等都被当做文件对待。在文件I/O中,要从一个文件读取数据,应用程序首先要调用:操作系统函数+文件名+到该文件的路径来打开文件。该函数取回一个顺序号,即文件句柄(fd)。文件句柄作为刚打开的文件是唯一的识别依据。

I/O多路复用就是通过一种同步IO模型,实现一个进程可以监视多个描述符(fd),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知应用程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。多路是指网络连接,复用指的是同一个线程。

5select poll epoll的理解

1 select

select本质上是 通过设置或检查存放fd标志位的数据结构 进行下一步处理。 这带来缺点:

单个进程可监视的fd数量被限制,即能监听端口的数量有限。单个进程所能打开的最大连接数有FD_SETSIZE宏定义,32位默认1024个,64位默认2048。

对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给 socket 注册某个回调函数,当他们活跃时,自动完成相关操作,即可避免轮询,这就是epoll与kqueue。

每次调用select,都需要把被监控的fds集合从用户态空间拷贝到内核态空间,高并发场景下这样的拷贝会使得消耗的资源是很大的。

2 poll

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制,因其基于链表存储。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

3 epoll

https://vdn1.vzuu.com/SD/346e30f4-9119-11eb-bb4a-4a238cf0c417.mp4?disable_local_cache=1&auth_key=1629468015-0-0-630cf1805a13ad511d9a66df764b7d25&f=mp4&bu=pico&expiration=1629468015&v=hw
epoll的接口非常简单,一共就三个函数:

epoll_create:生成一个 epoll 专用的文件描述符, 红黑树。
epoll_ctl:向 epoll 对象中添加/修改/删除要管理的连接,epoll 的事件注册函数,它不同于 select() 是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
epoll_wait:等待其管理的连接上的 IO 事件,收集在 epoll 监控的事件中已经发送的事件。

1epoll的设计

1、epoll在Linux内核中构建了一个文件描述符,该文件系统采用红黑树来构建,将用户程序的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的拷贝只需要一次。

2、epoll红黑树上采用事件异步唤醒,在用户空间获取事件时,不需要去遍历被监听描述符集合中所有的文件描述符,而是遍历那些被内核I/O事件异步唤醒之后加入到就绪队列并返回到用户空间的描述符集合。

3、epoll的数据从用户空间到内核空间采用mmap存储I/O映射来加速。

2触发模式

ET 边缘触发:它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次,直到下次再有数据流入之前都不会再提示,无论fd中是否还有数据可读。

LT默认的模式,水平触发:当设置了水平触发以后,以可读事件为例,当有数据到来并且数据在缓冲区待读。即使我这一次没有读取完数据,只要缓冲区里还有数据就会触发第二次,直到缓冲区里没数据。

3epoll基本流程

  1. 执行 epoll_create

内核在epoll文件系统中建了1个file结点,(使用完,必须调用close()关闭,否则导致fd被耗尽)

在内核cache里建了红黑树存储epoll_ctl传来的socket,在内核cache里建了rdllist双向链表存储准备就绪的事件。

  1. 执行 epoll_ctl

如果增加socket句柄,检查红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,告诉内核如果这个句柄的中断到了,就把它放到准备就绪list链表里。

ps:所有添加到epoll中的事件都会与设备(如网卡)驱动程序简历回调关系,相应的事件发生时,会调用回调方法。

  1. 执行 epoll_wait

立刻返回准备就绪表里的数据即可(将内核cache里双向列表中存储的准备就绪的事件 复制到用户态内存)

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。

如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

4优点

没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口).

效率提升,不是轮询,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数 即epoll最大的优点就在于它只关心“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll.

内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

6 总结

在这里插入图片描述

select,poll,epoll都是IO多路复用机制,即可以监视多个描述符,一旦某个描述符就绪(读或写就绪),能够通知程序进行相应读写操作。 但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,而epoll只要一次拷贝,这也能节省不少的开销。

猜你喜欢

转载自blog.csdn.net/zs18753479279/article/details/119805404