软件开发-02 I/O深思

1. I/O

input/output,计算机内存与外部设备之间拷贝数据的过程。
在这里插入图片描述
用户态: 用户态访问硬件等需要通过内核提供的接口间接访问,有安全限制。

内核态: 访问无限制,任意。

Linux 5 种 IO 模型:

在这里插入图片描述

1.1 阻塞IO(BIO)

在这里插入图片描述

bio 在 accept 接收连接和 read 读取数据的时候都是阻塞的,通常都需要一个线程去处理一个连接。

1.2 非阻塞IO

在这里插入图片描述

当读取数据时,内核缓冲区未准备好数据,则直接返回错误标志( EWOULDBLOCK ),应用进程不用一直等待,但是需要通过轮询的方式知道读取到数据为止,消耗cpu。

1.3 IO复用(NIO)

在这里插入图片描述

并发情况下读取数据时,将需要创建多个线程进行recvfrom,且这些线程可能会被阻塞,浪费系统资源。

Io复用采用select,pool,epool。select 用于监听多个fd(读取请求),当有一个fd数据准备好了,就会返回可读状态,处理数据的线程就会去发起recvfrom读取数据。这个阶段是非阻塞的,当然它也有阻塞的时候就是必须有任意一个可读。它也减少了并发请求创建线程的数量,体现了线程复用的技术。

异步是因为select的 Reactor(反应堆) ,当socket io注册到select上时,socket不会卡主可以做其它事情,当有可读取数据时,reactor会通知进行回调。

数据从内核空间copy到用户空间是同步阻塞的,也就是说recvfrom就是一个同步阻塞的方法。

1.4 信号驱动IO

在这里插入图片描述

信号驱动IO模型是伪异步的,当系统内核数据准备好后通知用户进程进行数据copy,还是需要主动的去copy数据的。且它的实现更复杂。

1.5 异步IO(AIO)

在这里插入图片描述

异步IO模型与信号驱动IO模型的不同之处在于,内核将数据copy到用户空间,然后去通知用户进程进行数据读取,整个过程完全异步的。

2. 零拷贝

在这里插入图片描述

直接将数据从内核缓冲区到网卡,不需要经历用户态的拷贝。

3. Socket

Socket 是应用层与 TCP/IP 协议通信的中间软件抽象层,它是一组接口其实就是一个门面模式。

Socket,中文译为“套接字”,是计算机网络中进行通信的一种抽象接口。它是支持TCP/IP协议的网络通信的基本操作单元,允许不同的计算机上的应用程序通过网络进行数据交换和通信。Socket编程是实现网络间数据传输的一种方式,广泛应用于互联网和局域网的各种应用中。

Socket的工作原理大致可以概括为以下几个关键点:

  1. 抽象表示:Socket是网络中不同主机上的应用进程之间进行双向通信的端点抽象。它包含了进行网络通信必要的信息,如使用的协议(如TCP或UDP)、本地与远程主机的IP地址以及各自的端口号。
  2. 连接建立:在TCP协议中,建立一个Socket连接需要经过三次握手的过程,即客户端发送一个带SYN标志的TCP报文到服务器,服务器回应一个带有SYN/ACK标志的TCP报文,最后客户端再发送一个带有ACK标志的TCP报文,从而完成连接的建立。
  3. 数据传输:一旦连接建立,双方就可以通过Socket进行数据的发送和接收。数据被分割成合适的数据包进行传输,到达接收方后再重新组装。
  4. 连接终止:当通信结束时,TCP连接的关闭需要进行四次挥手的过程,以确保数据的完整传输并且双方都同意关闭连接。
  5. 协议支持:Socket可以支持不同的传输层协议,最常用的是TCP(传输控制协议)和UDP(用户数据报协议)。TCP提供可靠、有序、面向连接的数据传输服务,而UDP则提供无连接、不可靠但是传输速度快的服务。

在编程层面,Socket通常表现为一个编程接口,开发者可以通过调用Socket API来创建、监听、连接、发送和接收数据,以及关闭Socket连接。几乎所有的现代编程语言,如C、C++、Python、Java等,都提供了对Socket编程的支持。

4. BIO

阻塞的IO,直接操作 TCP 缓存,输入输出流

第一阶段:服务端使用单线程只能处理一个客户端,其它的客户端将阻塞

第二阶段:服务器使用多线程每个线程处理一个客户端,这样可能产生无数的线程

第三阶段:服务器使用线程池处理连接的客户端,线程池中的线程数无法支持高并发,某个客户端可能占用非常长的时间

BIO(Blocking I/O,阻塞I/O)模型的局限性主要体现在以下几个方面:

  1. 性能瓶颈:在BIO模型中,每个客户端连接都需要一个独立的线程来处理。随着并发连接数增加,系统需要创建大量线程来维护这些连接,这会迅速消耗系统资源,如内存和CPU时间用于线程上下文切换,导致性能瓶颈。
  2. 用户经验下降:由于线程资源的限制,高并发情况下,服务器可能无法接受更多的连接请求,导致拒绝服务或长时间等待,从而影响用户体验。
  3. 扩展性差:BIO模型的扩展性受限于线程的数量和系统资源,难以通过简单增加硬件资源来线性提升处理能力,特别是在面对大量并发请求时。
  4. 资源浪费:在阻塞等待I/O操作(如读写操作)完成的过程中,线程处于空闲状态,无法处理其他任务,造成CPU资源的浪费。
  5. 编程模型简单但效率低:虽然BIO编程模型直观且易于理解,但由于其阻塞特性,整体的系统吞吐量和响应速度受限,特别是在网络延迟较高或数据传输量较大的情况下。
  6. 不易于处理大量并发:尤其在网络服务中,如Web服务器、即时通讯应用等,BIO难以有效处理数千甚至数万级别的并发连接。
  7. 故障传播:在一个线程处理单个连接的模型中,如果某个连接上的操作异常或长时间阻塞,可能会影响到整个服务的稳定性。

因此,为了解决BIO的这些局限性,现代系统设计倾向于采用非阻塞I/O(NIO)或异步I/O(AIO)模型,这些模型通过事件驱动、多路复用和异步处理机制,提高了系统的并发处理能力和响应速度,更适合高性能和高并发场景。

5. NIO

非阻塞IO,具有应用缓存,更加灵活,一个线程就可以处理很多的客户端

多路复用:

NIO(Non-blocking I/O,非阻塞I/O)和多路复用(Multiplexing)是紧密相关的概念,特别是在Java NIO的上下文中,多路复用是实现NIO模型的关键技术之一。

Selector Channel Buffer

OP_READ OP_WRITE OP_CONNECT OP_ACCEPT

5.1 select/poll/epoll

Select

  • select 有限制, fds 集合大小只有1024,即最多管理1024 个客户端
  • select 在没有任何一个 socket 的时候是阻塞的,select 在每个被它管理的 socket 的睡眠队列里都塞入一个与它相关的 entry,这样不论哪个 socket 来数据了,它立马就能被唤醒然后干活,但是它会傻傻地遍历所有 socket ,看看到底是哪个 scoket 来活了,然后把所有来活的 socket 封装成事件返回。

poll

poll 解决了 1024限制的问题

epoll

epoll 是 Linux 系统中一种高效的 I/O 多路复用技术,相较于传统的 select 和 poll 系统调用,它做了以下几方面的优化:

  1. 减少系统调用的次数:在 epoll 中,一旦将被监听的 socket 注册进 epoll 集合之后,就不需要在每次监听之前重新注册。这与 select 和 poll 不同,后者每次调用时都需要传入全部的文件描述符集合。

  2. 仅关注就绪的文件描述符:epoll 使用事件驱动的方式工作,内核只告诉用户空间那些真正发生事件(如可读、可写、异常等)的文件描述符。这意味着在处理过程中,epoll 只需返回并且处理那些已就绪的 socket,而不是像 select 或 poll 那样遍历整个文件描述符集合。

  3. 内核维护就绪列表:epoll 内部实现了一个就绪列表,所有发生事件的 socket 会自动将自己挂载到这个就绪队列上。当调用 epoll_wait 时,直接从这个就绪队列中返回事件,极大减少了扫描时间复杂度,通常情况下接近 O(1)。

  4. 批量操作:epoll 支持一次性批量注册和查询多个事件,这在处理大量并发连接时特别有效。

  5. 内存高效:epoll 使用更高效的内存管理方式,避免了大范围的数据复制,特别是在大量文件描述符的情况下,减少了内存消耗和CPU缓存失效。

  6. 边缘触发(ET)模式:除了水平触发(LT)模式外,epoll 还支持边缘触发模式,仅当缓冲区状态发生变化时才通知,减少了不必要的通知,提高了效率,但要求应用程序正确处理事件,避免遗漏。

  7. 减少上下文切换:epoll 设计使得在没有事件发生时,调用 epoll_wait 可以阻塞,直到有事件发生,减少了用户态与内核态之间的上下文切换,这对于高并发服务尤其重要。

这些优化使 epoll 成为构建高性能服务器的理想选择,尤其是在需要处理大量并发连接且活跃连接较少的场景下,如 Web 服务器、数据库服务器、即时通讯服务器等。

5.2 NIO 与 BIO

Java中的NIO(非阻塞I/O,New I/O)相比于传统的BIO(阻塞I/O,Blocking I/O)模式,主要在以下几个方面表现得更为优越:

  1. 非阻塞特性:NIO的一个核心特点是其非阻塞性。在NIO中,通道(Channel)和选择器(Selector)的使用允许程序在等待数据准备就绪时不必阻塞,可以继续执行其他任务。这意味着即使某些操作(如读写)没有完成,线程也不会被挂起,大大提高了系统的并发处理能力。
  2. 更高的并发处理能力:BIO中每个客户端连接通常需要一个单独的线程来处理,这在面对大量并发连接时会迅速耗尽系统资源。而NIO利用选择器(Selectors)可以监控多个通道的事件,一个线程就能管理多个通道,显著降低了线程开销,提升了系统对高并发连接的处理能力。
  3. 数据缓冲区:NIO引入了缓冲区(Buffer)的概念,数据读取到缓冲区或从缓冲区写入,这使得数据处理更有效率,同时也支持批量操作,减少了内存的复制操作。
  4. 更接近硬件操作:NIO的设计更接近于操作系统底层的I/O操作,可以更高效地利用系统资源,尤其是在进行大文件操作或者网络数据传输时,能够提供更好的吞吐量和更低的延迟。
  5. 更适合于特定场景:NIO特别适合于那些需要处理大量并发连接但每次连接传输数据量不大的应用场景,如Web服务器、聊天服务器、游戏服务器等。

尽管NIO有诸多优势,但它也有学习曲线较陡峭、编程模型相对复杂等缺点,并且在某些特定场景下,如处理高延迟、大数据块的传输,BIO的简单性和可靠性可能更占优势。因此,选择NIO还是BIO需根据具体的应用场景和需求来决定。

5.3 单 Reactor 单线程模型

在这里插入图片描述

单 Reactor 单线程模型是一种事件驱动的设计模式,广泛应用于高性能网络服务和并发编程中,特别是在处理大量并发连接但每个连接的业务处理逻辑相对简单且耗时较短的场景下。这种模型的核心组件是 Reactor(反应器),它负责监听和分发事件,而所有的工作都在一个单独的线程中完成。

基本组成及工作流程:

  1. Reactor(反应器):这是模型的核心部分,负责监控一个或多个文件描述符(socket)的I/O事件(如读、写、连接就绪等)。当有事件发生时,Reactor会唤醒并调度相应的事件处理器。
  2. Acceptor(接受者):Acceptor通常是在Reactor的控制下工作的,它的主要职责是监听来自客户端的新连接请求。一旦有新的连接请求到达,Acceptor会接受这个连接,并将新连接的socket注册到Reactor中,以便后续对这个连接上的读写事件进行监听和处理。
  3. Handler(处理器):当Reactor检测到某一socket上有事件发生时,它会调用事先注册好的Handler来处理这个事件。Handler包含了处理连接读写的具体逻辑,比如读取请求数据、处理请求、发送响应等。

缺点

  • 吞吐量限制:所有事件处理都在一个线程中,如果某个事件处理时间过长,会阻塞其他事件的处理,影响整体的响应时间和吞吐量。
  • CPU 利用率:在CPU密集型任务或多核处理器上,单线程无法充分利用多核优势。

5.4 单 Reactor 多线程模型

在这里插入图片描述

单 Reactor 多线程模型是对单 Reactor 单线程模型的一种扩展,旨在解决单线程模型在处理高并发或CPU密集型任务时的局限性。该模型依然维持一个Reactor负责事件监听和分发,但引入了多个工作线程来并行处理事件,从而提高了系统的处理能力和响应速度。以下是该模型的基本结构和工作流程:

扩展

  1. Worker Thread Pool(工作线程池):包含多个工作线程,用于处理Reactor分发的具体事件,如读写数据、业务逻辑处理等。

  2. 事件队列:Reactor将接收到的事件放入事件队列,工作线程从队列中取出并处理事件。

缺点

  • 复杂度增加:相比于单线程模型,多线程模型增加了线程间协调的复杂度,如线程同步、资源共享等问题。
  • 资源消耗:多线程会增加内存占用,且线程创建和销毁、上下文切换也会带来一定的开销。

5.5 主从 Reactor 多线程

在这里插入图片描述
扩展

  1. 主Reactor(Main Reactor):负责监听新的连接请求,并接受新的客户端连接。一旦接受连接成功,主Reactor会立即将新连接的socket分发给一个从属的Reactor或工作线程池。
  2. 从属Reactor(Sub Reactors):一组Reactor线程,每个从属Reactor负责监听并处理已连接socket上的读写事件。主Reactor将已接受的连接分配给这些从属Reactor,从而分散事件处理的压力。

优点

  • 高度并发:通过主从Reactor的分工合作,以及多个工作线程池,能够高效处理大量并发连接和请求。
  • 负载均衡:从属Reactor和工作线程池的引入,使得负载能够更加均匀地分布,提高系统整体处理能力。
  • 扩展性好:容易根据需要增加从属Reactor和工作线程的数量,以应对更高的并发需求。

缺点

  • 复杂度高:相比单Reactor模型,主从Reactor模型的架构更为复杂,增加了系统设计和维护的难度。
  • 资源消耗:更多的线程和上下文切换意味着更高的内存消耗和CPU开销。
  • 41
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值