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模型单线程模型如下: 

java netty框架的使用 netty框架原理_数据

  • 用户发起IO操作到事件分离器
  • 事件分离器调用相应的处理器处理事件
  • 事件处理完成,事件分离器获得控制权,继续相应处理

缺点:

》 性能有极限,不能处理成百上千的事件 
》 当负荷达到一定程度时,性能将会下降 
》某一个事件处理器发送故障,不能继续处理其他事件


二、Reactor模型多线程模型如下: 

java netty框架的使用 netty框架原理_数据_02

机制:

》有专门一个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线程池)操作和业务处理依然在后面的从线程中完成

java netty框架的使用 netty框架原理_线程池_03

优点:监听和处理都是线程池,且是独立的nio线程池

那么在netty中是通过NioEventLoop实现的,



NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:

  1. 作为服务端Acceptor线程,负责处理客户端的请求接入
  2. 作为客户端Connecor线程,负责注册监听连接操作位,用于判断异步连接结果
  3. 作为IO线程,监听网络读操作位,负责从SocketChannel中读取报文
  4. 作为IO线程,负责向SocketChannel写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据全部发送完成 
    如下图,是一个NioEventLoop的处理链:

java netty框架的使用 netty框架原理_线程池_04

  • handler处理链中的处理方法是串行化执行的
  • 一个客户端连接只注册到一个NioEventLoop上,避免了多个IO线程并发操作


OK,和netty相关的基础知识就介绍完了,后面会陆续介绍netty的其他相关知识,欢迎交流学习...