五种IO通信模型(以 Socket 通信为例)

754 阅读4分钟

前言

  • 服务端的作用是监听客户端发送过来的数据,进行处理,处理完响应给客户端。
  • 服务端监听客户端发送过来的数据,就是使用这个 socket 的 read() 方法来实现的,因此服务端的处理链路可以看出这样:read() -> 处理 -> 响应 -> read(),可以知道这个 read() 方法在底层一定是在while循环中
  • 如果一个服务端同时只能处理一个请求发送过来的数据,那这个吞吐量太低了,因此通过学习下面五种IO通信模型,就能知道怎么设计一个高吞吐量的服务端了

1、同步阻塞模型:BIO

哪里阻塞

  • 服务端调用 read() 系统调用读取内核数据到用户空间,当数据还没到达内核时会阻塞,直到有数据到内核缓冲区,才真正去读,读完之后才能进行往下执行

哪里同步

  • 服务端调用 read() 系统调用时,即使内存缓冲区有数据,也需要 cpu 把数据先读取到寄存器,再把数据放入用户内存,这个过程 cpu 是不能做其他事情的,因此是同步的

缺点

  • 阻塞的过程中,cpu 不能做其他事情,只能一直等待下去
  • 一般的做法是为每个连接分配一个线程来处理,这样虽然能同时处理多个连接,但是如果请求多了,线程数会太多,即使是线程池也会顶不住

优点

  • 实现简单

应用

  • Java BIO
  • 阻塞模式下的 Socket

2、同步非阻塞模型

哪里非阻塞

  • 服务端调用 read() 系统调用,如果没有数据,会直接返回 error,这样这个while循环就会一直进行下去,这期间 cpu 是不阻塞的,可以去干其他的活

哪里同步

  • 和 BIO 一样,如果内核缓冲区有数据了,读数据的过程 cpu 还是不能干其他事情,因此是同步的

缺点

  • 进程轮询,消耗 cpu 资源,不适合并发量高的场景

应用

  • 非阻塞模式下的 Socket

3、多路复用IO模型

实现原理(以java NIO 多路复用为例)

  • 首先 socket 需要向多路复用器注册,并说明该 socket 感兴趣的事件(可读事件、可写事件、客户端连接事件等)
  • 调用多路复用器的 select(),如果它维护的 socket 中没有事件发送,则阻塞。如果有事件发生,会把发生事件的 socket 存放到一个集合中
  • 遍历这个集合,判断集合中的 socket 发生的是什么事件,然后进行相应的处理

优点

  • 可以用一个线程管理多个客户端连接

缺点

  • 是一种同步非阻塞的模式,虽然一个线程能管理多个客户端连接,但是一个线程同时只能处理一个客户端传过来的数据,处理数据的过程还是同步的

应用

  • java NIO
  • redis

4、信号驱动IO模型

实现原理

  • 服务端发出一个 sigaction() 系统调用并直接返回,内核会在有数据的情况下,通过回调的方式来通知用户进程,这时用户进程才调用 read() 方法去读数据
  • 这种方式一个线程就能同时处理多个 socket

缺点

  • 这种方式只是伪异步,内核缓冲区有数据时,还是通过 read() 方法去读数据,读数据的过程还是同步的

5、异步IO模型(AIO)

实现原理

  • 用户进程发起 aio_read() 系统调用并直接返回,当内核缓冲区中有数据时,内核会自动把数据拷贝到对应的用户空间,然后通知cpu

优点

  • 这种方式避免了 read() 方法在 while 循环中一直轮询下去,而且一个线程就能同时处理多个 socket
  • cpu不需要再去同步的去读内核缓冲区的数据了,整个过程异步非阻塞

缺点

  • 需要操作系统支持,目前linux 的 AIO 实现还不成熟
  • 只有 windows 的 AIO 成熟