五种IO通信模型(以 Socket 通信为例)
前言
- 服务端的作用是监听客户端发送过来的数据,进行处理,处理完响应给客户端。
- 服务端监听客户端发送过来的数据,就是使用这个 socket 的 read() 方法来实现的,因此服务端的处理链路可以看出这样:
read() -> 处理 -> 响应 -> read(),可以知道这个 read() 方法在底层一定是在while循环中
- 如果一个服务端同时只能处理一个请求发送过来的数据,那这个吞吐量太低了,因此通过学习下面五种IO通信模型,就能知道怎么设计一个高吞吐量的服务端了
1、同步阻塞模型:BIO
哪里阻塞
- 服务端调用 read() 系统调用读取内核数据到用户空间,当数据还没到达内核时会阻塞,直到有数据到内核缓冲区,才真正去读,读完之后才能进行往下执行
哪里同步
- 服务端调用 read() 系统调用时,即使内存缓冲区有数据,也需要 cpu 把数据先读取到寄存器,再把数据放入用户内存,这个过程 cpu 是不能做其他事情的,因此是同步的
缺点
- 阻塞的过程中,cpu 不能做其他事情,只能一直等待下去
- 一般的做法是为每个连接分配一个线程来处理,这样虽然能同时处理多个连接,但是如果请求多了,线程数会太多,即使是线程池也会顶不住
优点
应用
2、同步非阻塞模型
哪里非阻塞
- 服务端调用 read() 系统调用,如果没有数据,会直接返回 error,这样这个while循环就会一直进行下去,这期间 cpu 是不阻塞的,可以去干其他的活
哪里同步
- 和 BIO 一样,如果内核缓冲区有数据了,读数据的过程 cpu 还是不能干其他事情,因此是同步的
缺点
- 进程轮询,消耗 cpu 资源,不适合并发量高的场景
应用
3、多路复用IO模型
实现原理(以java NIO 多路复用为例)
- 首先 socket 需要向多路复用器注册,并说明该 socket 感兴趣的事件(可读事件、可写事件、客户端连接事件等)
- 调用多路复用器的 select(),如果它维护的 socket 中没有事件发送,则阻塞。如果有事件发生,会把发生事件的 socket 存放到一个集合中
- 遍历这个集合,判断集合中的 socket 发生的是什么事件,然后进行相应的处理
优点
缺点
- 是一种同步非阻塞的模式,虽然一个线程能管理多个客户端连接,但是一个线程同时只能处理一个客户端传过来的数据,处理数据的过程还是同步的
应用
4、信号驱动IO模型
实现原理
- 服务端发出一个 sigaction() 系统调用并直接返回,内核会在有数据的情况下,通过回调的方式来通知用户进程,这时用户进程才调用 read() 方法去读数据
- 这种方式一个线程就能同时处理多个 socket
缺点
- 这种方式只是伪异步,内核缓冲区有数据时,还是通过 read() 方法去读数据,读数据的过程还是同步的
5、异步IO模型(AIO)
实现原理
- 用户进程发起 aio_read() 系统调用并直接返回,当内核缓冲区中有数据时,内核会自动把数据拷贝到对应的用户空间,然后通知cpu
优点
- 这种方式避免了 read() 方法在 while 循环中一直轮询下去,而且一个线程就能同时处理多个 socket
- cpu不需要再去同步的去读内核缓冲区的数据了,整个过程
异步非阻塞
缺点
- 需要操作系统支持,目前linux 的 AIO 实现还不成熟
- 只有 windows 的 AIO 成熟