Netty

Netty

Netty 基于NIO(NIO 是一种同步非阻塞的I/O 模型,在Java 1.4 中引入了
NIO )。使用 Netty 可以极大地简化并简化了TCP 和UDP 套接字服务器等网
络编程,并且性能以及安全性等很多方面都非常优秀

Netty简介

  1. Netty 是一个 基于 NIO 的 client-server(客户端服务器)框架,使用
    它可以快速简单地开发网络应用程序
  2. 它极大地简化并优化了TCP 和UDP 套接字服务器等网络编程,并且性能以
    及安全性等很多方面甚至都要更好
  3. 支持多种协议 如FTP,SMTP,HTTP以及各种二进制和基于文本的传统协议

为什么不直接用NIO

  1. NIO的编程模型复杂而且存在一些BUG,并且对编程功底要求比较高
  2. NIO在面对断连重连、包丢失、粘包等问题时处理过程非常复杂

Netty的优点

  1. 统一的 API,支持多种传输类型,阻塞和非阻塞的。
  2. 简单而强大的线程模型。
  3. 自带编解码器解决TCP 粘包/拆包问题。
  4. 自带各种协议栈。
  5. 真正的无连接数据包套接字支持。
  6. 比直接使用 Java 核心 API 有更高的吞吐量、更低的延迟、更低的资源消耗
    和更少的内存复制
  7. 安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持
  8. Dubbo、RocketMQ、Elasticsearch、gRPC、Spark、Elasticsearch 等等热
    门开源项目都用到了Netty

Netty和Tomcat的区别

Netty和Tomcat最大的区别就在于通信协议

  1. Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器
  2. Netty能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解
    码字节流

Netty为什么传输快

零拷贝。一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲
区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会
造成不必要的资源浪费。在堆内存之外开辟一块内存,数据就直接从IO读到了那
块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从
而加快了传输速度。

Netty的应用场景

  1. 作为RPC 框架的网络通信工具:我们在分布式系统中,不同服务节点之间经常
    需要相互调用,这个时候就需要 RPC 框架了。不同服务节点的通信是使用Netty
    来做
  2. 实现一个自己的HTTP 服务器:通过Netty 我们可以自己实现一个简单的HTTP
    服务器
  3. 实现一个即时通讯系统: 使用Netty 我们可以实现一个可以聊天类似微信的
    即时通讯系统
  4. 实现消息推送系统 :市面上有很多消息推送系统都是基于 Netty 来做的

Netty核心组件

Bootstrap 和ServerBootstrap

一种是用于客户端的Bootstrap,一种是用于服务端的ServerBootstrap。
前者的功能是连接到远程主机和端口,后者的功能是绑定本机端口。
Bootstrap 是客户端的启动引导类/辅助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动引导/辅助类:Bootstrap
Bootstrap b = new Bootstrap();
//指定线程模型
b.group(group).
......
// 尝试建立连接
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
// 优雅关闭相关线程组资源
group.shutdownGracefully();
}

ServerBootstrap 服务端的启动引导类/辅助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1.bossGroup 用于接收连接,workerGroup 用于具体的处理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.创建服务端启动引导/辅助类:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.给引导类配置两大线程组,确定了线程模型
b.group(bossGroup, workerGroup).
......
// 6.绑定端口
ChannelFuture f = b.bind(port).sync();
// 等待连接关闭
f.channel().closeFuture().sync();
} finally {
//7.优雅关闭相关线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
  1. Bootstrap 通常使用connet() 方法连接到远程的主机和端口,作为一
    个Netty TCP 协议通信中的客户端。另外Bootstrap 也可以通过 bind()
    方法绑定本地的一个端口,作为UDP 协议通信中的一端
  2. ServerBootstrap通常使用 bind() 方法绑定本地的端口上,然后等待
    客户端的连接
  3. Bootstrap 只需要配置一个线程组—EventLoopGroup,而ServerBootstrap
    需要配置两个线程组— EventLoopGroup ,一个用于接收连接,一个用于具体
    的 IO 处理

Channel

网络操作抽象类。一旦客户端成功连接服务端,就会新建一个Channel 同该用
户端进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
//  通过 Bootstrap 的 connect 方法连接到服务端
public Channel doConnect(InetSocketAddress inetSocketAddress) {
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener)
future -> {
if (future.isSuccess()) {
completableFuture.complete(future.channel());
} else {
throw new IllegalStateException();
}
});
return completableFuture.get();
}

比较常用的Channel接口实现类是 :

  1. NioServerSocketChannel(服务端)
  2. NioSocketChannel(客户端)

EventLoop

事件循环。EventLoop 的主要作用实际就是责监听网络事件并调用事件处理器进
行相关I/O 操作(读写)的处理。Channel为Netty 网络操作(读写等操作)抽
象类,EventLoop 负责处理注册到其上的Channel 的I/O 操作,两者配合进行
I/O 操作。
EventLoopGroup 包含多个 EventLoop(每一个 EventLoop 通常内部包含一
个线程),它管理着所有的EventLoop的生命周期。并且EventLoop 处理的I/O
事件都将在它专有的Thread 上被处理,即Thread 和EventLoop 属于1:1的关
系,从而保证线程安全。

EventLoopGroup

Netty 中EventLoopGroup 是Reactor 模型的一个实现。
Reactor 就是一个执行while (true) { selector.select();…}循环的线程
,会源源不断产生新的事件,称作反应堆很贴切。 事件又分为连接事件、IO 读和
IO 写事件,一般把连接事件单独放一线程里处理,即主Reactor(MainReactor)
,IO读和IO写事件放到另外的一组线程里处理,即从Reactor(SubReactor),从
Reactor线程数量一般为2*(CPUs - 1)。 所以在运行时,MainReactor只处理
Accept事件,连接到来,马上按照策略转发给从Reactor之一,只处理连接,故
开销非常小;每个SubReactor管理多个连接,负责这些连接的读和写,属于IO
密集型线程,读到完整的消息就丢给业务线程池处理业务,处理完比后,响应
消息一般放到队列里,SubReactor会去处理队列,然后将消息写回

Bytebuf

字节容器,网络通信最终都是通过字节流进行传输的。ByteBuf就是Netty提供
的一个字节容器,其内部是一个字节数组。可以将ByteBuf看作是Netty对Java
NIO 提供了ByteBuffer 字节容器的封装和抽象

ChannelHandler

消息处理器,主要负责处理客户端/服务端接收和发送的数据。
当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。 一
个Channel包含一个ChannelPipeline。 ChannelPipeline为ChannelHandler
的链,一个pipeline 上可以有多个 ChannelHandler。
我们可以在ChannelPipeline 上通过addLast() 方法添加一个或者多个
ChannelHandler(一个数据或者事件可能会被多个Handler 处理)。当
一个ChannelHandler处理完之后就将数据交给下一个ChannelHandler。
当ChannelHandler 被添加到的 ChannelPipeline 它得到一个 ChannelHandlerContext,它代表一个 ChannelHandler 和 ChannelPipeline
之间的“绑定”。 ChannelPipeline 通过 ChannelHandlerContext来间接管理
ChannelHandler 。

ChannelFuture

Netty的核心组件

  1. Bootstrap 和 ServerBootstrap(启动引导类)Bootstrap 是客户端的
    启动引导类/辅助类,ServerBootstrap 客户端的启动引导类/辅助类
  • bootstrap 通常使用 connet() 方法连接到远程的主机和端口,作为一个
    Netty TCP 协议通信中的客户端。另外,Bootstrap 也可以通过bind() 方
    法绑定本地的一个端口,作为UDP 协议通信中的一端
  • ServerBootstrap通常使用 bind() 方法绑定本地的端口上,然后等待客户
    端的连接
  • Bootstrap 只需要配置一个线程组EventLoopGroup,而ServerBootstrap
    需要配置两个线程组EventLoopGroup ,一个用于接收连接,一个用于具体的
    IO 处理

Channel 和EventLoop 的关系

Channel 为 Netty 网络操作(读写等操作)抽象类,EventLoop 负责处理注册
到其上的Channel 的 I/O 操作,两者配合进行 I/O 操作

EventloopGroup 和EventLoop 的关系

EventLoopGroup 包含多个 EventLoop(每一个 EventLoop 通常内部包含
一个线程),它管理着所有的 EventLoop 的生命周期。EventLoop 处理的
I/O 事件都将在它专有的Thread 上被处理,即Thread 和EventLoop属于
1 : 1 的关系,从而保证线程安全

Author: 高明
Link: https://skysea-gaoming.github.io/2021/05/31/Netty/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.