netty是典型基于reatctor模型的编程,主要用于完成网络底层通信的,java本身也是提供各种io的操作,但是使用起来api会很繁琐,同时性能有很难有保证,经常会出现莫名其妙的bug,所以为了方便开发者更好的把精力集中于业务,让netty来封装一切繁琐的工作,对开发者透明化,大大降低了开发门槛,所以从本章开始就完全的介绍一下netty的相关知识,今天主要介绍的内容知识点如下:
1 IO模型分类
2 编程模型分类
3 Netty的线程模型分类
4 Netty的NIO线程实现机制
同步阻塞:调用会一直阻塞到数据接收完毕
同步非阻塞:调用会一直循环,直到接收到数据为止
IO复用
& select/poll
流程:
》注册待侦听的fd(这里的fd创建时最好使用非阻塞)
》每次调用都去检查这些fd的状态,当有一个或者多个fd就绪的时候返回
》返回结果中包括已就绪和未就绪的fd
优点:解决了单个进程能够打开的文件描述符数量有限制这个问题
缺点:
》每次调用,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多的时候会很大
》需要在内核遍历所有传进来的fd,这个fd很多的时候,开销也很大
》支持的文件描述符数量有限,默认为1024
& epoll
流程:
》调用epoll_create建立一个epoll对象
》调用epoll_ctl向epoll对象中添加连接的套接字
》调用epoll_wait收集发生的事件的连接
优点:
基于事件驱动的方式,避免了每次都要把所有fd都扫描一遍
epoll_wait只返回就绪的fd
epoll使用nmap内存映射技术避免了内存复制的开销
poll的fd数量上限是操作系统的最大文件句柄数目,这个数目一般和内存有关,通常远大于1024
信号驱动:
》开启套接字信号驱动IO功能
》系统调用sigaction执行信号处理函数(非阻塞,立刻返回)
》数据就绪,生成sigio信号,通过信号回调通知应用来读取数据。
异步非阻塞/事件驱动IO:告知内核启动某个操作,等操作完成之后来主动通知我们
接下来再整理一下,常用的编程模型
BIO:同步阻塞
流程:
》主线程accept请求阻塞
》请求到达,创建新的线程来处理这个套接字,完成对客户端的响应
》主线程继续accept下一个请求
缺点:
》当客户端连接增多时,服务端创建的线程也会暴涨,系统性能会急剧下降
》服务端的线程个数和客户端并发访问的个数是1:1
NIO:异步非阻塞
流程:
》创建ServerSocketChannel监听客户端连接并绑定监听端口,设置为非阻塞模式
》创建Reactor线程,创建多路复用器(Selector)并启动线程
》将ServerSocketChannel注册到Reactor线程的Selector上。监听accept事件
》selector线程在run方法中无限循环轮询准备就绪的key
》Selector监听到新的客户端接入,处理新的请求,完成tcp三次握手,建立物理连接
》将新的客户端连接注册到Selector上,监听读操作。读取客户端发送的网络消息
》客户端发送的数据就绪则读取客户端请求,进行处理。
优点:实现了io的多路复用功能
缺点:编程实现起来非常复杂
AIO/NIO.2:当进行读写操作时,只须直接调用API的read或write方法即可
》对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序
》对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序
同步/异步:是对于服务端读取数据而言
阻塞/非阻塞:是对于客户端请求的
接下来看原始java的nio实现,其实最主要就是三个类:
核心类
Channel:数据的传输管道
Buffer:数据缓冲区
Selector:根据key处理对应的channel
和核心流程:
1 打开serversocketchannel
2 绑定监听地址ip
3 创建selector启动线程
4 把serversocketchannel注册到selector 监听
5 selector轮询就绪的key
6 handleAccept处理新的客户端接入
7 设置新建客户端连接的socket参数
8 向selector注册监听读操作
9 handleRead异步读请求消息到bytebuffer
10 decode请求消息
11 异步写bytebuffer到socketchannel
看到上面的步骤还说很繁琐的,如果用原始的api开发,这些都是需要开发者自己考虑到并且要能够完善,但是我们用netty的话,上面这些就不是我们太关注的了,简化了很多,所以接下来就看看netty的线程模型机制:
总共有三种
一、Reactor模型单线程模型如下:
- 用户发起IO操作到事件分离器
- 事件分离器调用相应的处理器处理事件
- 事件处理完成,事件分离器获得控制权,继续相应处理
缺点:
》 性能有极限,不能处理成百上千的事件
》 当负荷达到一定程度时,性能将会下降
》某一个事件处理器发送故障,不能继续处理其他事件
二、Reactor模型多线程模型如下:
机制:
》有专门一个NIO线程-Acceptor线程用于监听服务端,接收client的TCP连接请求
》网络IO操作-读、写等由一个NIO线程池负责
》1个NIO线程能够同一时候处理N条链路。可是1个链路仅仅相应1个NIO线程,防止发生并发操作问题。
缺点:监听服务的只是一个单线程,还说会存在性能瓶颈
三、Reactor主从多线程模型
- Acceptor(boss线程池)不再是一个单独的NIO线程,而是一个独立的NIO线程池
- Acceptor(boss线程池)处理完后,将事件注册到IO线程池(work线程池)的某个线程上
- IO线程继续完成后续的IO操作
- Acceptor(boss线程池)仅仅完成登录、握手和安全认证等操作,IO(work线程池)操作和业务处理依然在后面的从线程中完成
优点:监听和处理都是线程池,且是独立的nio线程池
那么在netty中是通过NioEventLoop实现的,
NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:
- 作为服务端Acceptor线程,负责处理客户端的请求接入
- 作为客户端Connecor线程,负责注册监听连接操作位,用于判断异步连接结果
- 作为IO线程,监听网络读操作位,负责从SocketChannel中读取报文
- 作为IO线程,负责向SocketChannel写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据全部发送完成
如下图,是一个NioEventLoop的处理链:
- handler处理链中的处理方法是串行化执行的
- 一个客户端连接只注册到一个NioEventLoop上,避免了多个IO线程并发操作
OK,和netty相关的基础知识就介绍完了,后面会陆续介绍netty的其他相关知识,欢迎交流学习...