参考文档
https://zhuanlan.zhihu.com/p/550956053
https://zhuanlan.zhihu.com/p/514448867
BIO
读取数据有两个阶段
- 等待数据就绪,数据到达内核缓冲区
- 读取数据(系统调用),从内核缓冲区,拷贝至用户缓冲区
BIO两个阶段都会阻塞
BIO编程时,需要为每个创建一个线程,如果没有数据可读,该线程将会阻塞在读数据的方法上,造成线程资源浪费。
非阻塞IO
多路复用NIO
IO多路复用就是基于FD
文件描述符(FD) :是一个从0 开始的无符号整数,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。
阶段一:
用户进程调用select,指定要监听的FD集合;
内核监听FD对应的多个socket;
任意一个或多个socket数据就绪则返回readable;
此过程中用户进程阻塞
阶段二:
用户进程找到就绪的socket
依次调用recvfrom读取数据
内核将数据拷贝到用户空间
用户进程处理数据
IO多路复用:是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。但是监听FD、通知的方式又有以下几种:
- select(正是上述图中的那种)
- Linux最早是由的I/O多路复用技术
- poll
- poll模式是在select模式的基础上进行的改进,但是只是很简单的改进。
- epoll
- 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高
- 每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
- 利用ep_poll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而下降
JAVA-NIO
java1.4 开始支持NIO,但用起来比较复杂。
netty基于java-nio做了封装,简化了很多。直接看netty
Netty
官网例子 https://github.com/netty/netty
Reactor模型
1.Reactor单线程模型
多路复用、事件分发和处理共用一个线程
2.Reactor多线程模型
- 一个负责处理连接请求的accepter线程
- 一组负责读数据/处理数据/写数据的线程池 //TODO 读写和处理是同一组线程吗?
处理过程
- Reactor对象通过Selector监听客户端请求事件,通过dispatch进行分发;Reactor处理所有事件的监听和响应
- 如果是连接事件,则由Acceptor通过accept方法处理连接请求,然后创建一个Handler对象响应事件;
- 如果不是连接请求,则由Reactor对象调用对应handler对象进行处理;handler只响应事件,不做具体的业务处理,它通过read方法读取数据后,会分发给线程池的某个线程进行业务处理,并将处理结果返回给handler;
- handler收到响应后,通过send方法将结果返回给client。
3.主从Reactor多线程模型
- MainReactor来处理连接事件,
- 非连接事件,分发给SubReactor进行处理
Netty源码解析
//TODO
nio的几个重要组件:Selector(事件选择器),Channel(通信通道),Buffer(通信载体)
netty的几个重要组件:
EventLoop(线程组件,最核心组件),
Channel(通信通道,netty做了一层封装),
Pipeline(消息通知链),
ChannelHandler(真正触发事件干活的),
ChannelHandlerContext(对handler做了一层包装)