【Netty】专栏总集篇
前言:
在之前的博文中,本人讲解了什么是 NIO
,并介绍了 Java 操作 NIO
的 核心API 的使用
(NIO
博文:《一文彻底理解什么是 NIO》)
但是,在我们使用的过程中,我们也能发现:NIO
的 类库 和 API 繁杂, 使用麻烦:
需要熟练掌握 Selector、 ServerSocketChannel、 SocketChannel、 ByteBuffer等 API 的调用
并且 开发工作量 和 难度 都非常大,例如:
客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞 和 异常流的处理 等等
因此,几个前辈 根据 NIO
的 核心API,加以自己的 理解判断 以及 天马行空的 设计思路,设计出了一款 基于 NIO
实现的、既 传输效率高,又 操作简便 的 网络通信框架 —— Netty
Netty
对 JDK 自带的 NIO
的 API 进行了良好的封装,解决了上述问题
那么,在本专栏中,本人就来介绍 Netty
的 使用 和 实现原理
基本概念:
图标:
介绍:
Netty
是由JBOSS提供的一个Java开源框架
Netty
提供 异步的、基于事件驱动 的 网络应用程序框架
Netty
拥有 高性能、 吞吐量更高、延迟更低、减少资源消耗,最小化不必要的内存复制 等优点。
Netty 现在都在用的是4.x,5.x版本已经废弃,Netty 4.x 需要JDK 6以上版本支持
应用场景:
- 互联网行业:
在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,
Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用
典型应用:
- 阿里分布式服务框架 Dubbo 的 RPC 框架使用,Dubbo 协议进行节点间通信,
Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信- Rocketmq底层也是用的Netty作为基础通信组件
- 游戏行业:
无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用
Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈
- 大数据领域:
经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,
默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现
线程模型:
- Netty 抽象出 两组线程池 ——
BossGroup
和WorkerGroup
组件 | 功能 |
---|---|
BossGroup | 接收 客户端的连接 |
WorkerGroup | 处理 网络数据的读写 |
BossGroup
和WorkerGroup
的类型都是 NioEventLoopGroup- NioEventLoopGroup 相当于一个 事件循环线程组,
这个组中含有 多个 事件循环线程 , 每一个事件循环线程 都是 NioEventLoop - 每个 NioEventLoop 都有一个 selector,用于监听 注册在其上的socketChannel 的网络通讯
- 每个Boss 的 NioEventLoop线程,内部循环执行的步骤有 3 步:
- 处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
- 将 NioSocketChannel 注册到 某个worker 的 NIOEventLoop 的 selector 上
- 处理 任务队列的任务,即:runAllTasks
- 每个worker 的 NIOEventLoop线程,循环执行的步骤:
- 轮询注册到自己selector上的所有NioSocketChannel 的read、write事件
- 处理 I/O 事件, 即read , write 事件, 在 对应的NioSocketChannel 处理业务
- runAllTasks 处理 任务队列TaskQueue 中的任务 ,
一些耗时的业务处理一般可以放入TaskQueue中慢慢处理,这样不影响数据在 pipeline 中的流动处理
- 每个worker 的 NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),
管道中维护了很多 handler 处理器用来处理 channel 中的数据
接下来,本人就来讲解下 Netty
的使用:
首先,是 Netty
的 Maven依赖:
Maven依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.35.Final</version>
</dependency>
接下来,本人来展示下 Netty
的 基本使用:
基本使用:
首先是 服务端 的代码:
服务端:
package edu.youzg.demo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;
/**
* @Author: Youzg
* @CreateTime: 2021-05-02 19:11
* @Description: 带你深究Java的本质!
*/
public class NettyServerDemo {
public static void main(String[] args) {
/*
创建 boss、worker 线程组:
boss线程组 处理 连接请求
worker线程组 处理 客户端业务
(默认线程池大小 为 CPU核数的2倍)
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建 服务端启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
// 配置 启动参数
bootstrap.group(bossGroup, workerGroup) // 配置 接受连接、处理事件 的 事件循环线程组
.channel(NioServerSocketChannel.class) // 设置 服务端的通道 为 NioServerSocketChannel类型(TCP连接)
// 初始化 服务器连接队列 的大小,服务端处理客户端连接请求 是 顺序处理的,所以 同一时间 只能处理 一个客户端连接。
// 多个客户端 同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() { // 设置 通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 为 workerGroup 的 SocketChannel 设置 处理器
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("Netty Server start successfully!");
/*
绑定 9000端口
并
等待 异步操作 执行完毕
*/
ChannelFuture channelFuture = bootstrap.bind(9000).sync();
// 为 channelFuture 注册”监听器“
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("bind 9000 port successfully!");
} else {
System.out.println("bind 9000 port failure!");
}
}
});
/*
对 通道 关闭监听
并
等待 异步操作 执行完毕
*/
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
/*
优雅停机
*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* 自定义 服务端 入站消息处理器
*/
static class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取客户端发送的数据
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
* @param msg 就是客户端发送的数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程 " + Thread.currentThread().getName());
//Channel channel = ctx.channel();
//ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
//将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
}
/**
* 数据读取完毕处理方法
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("Hello Client, I am Server", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
/**
* 处理异常, 一般是需要关闭通道
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
* @param cause 异常信息
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
接下来,是 客户端 的代码:
客户端:
package edu.youzg.demo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
/**
* @Author: Youzg
* @CreateTime: 2021-05-02 21:18
* @Description: 带你深究Java的本质!
*/
public class NettyClientDemo {
public static void main(String[] args) {
// 创建 客户端 事件轮询线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建 客户端启动对象
Bootstrap bootstrap = new Bootstrap();
// 配置 启动参数
bootstrap.group(group)
.channel(NioSocketChannel.class) // 设置 服务端的通道 为 NioServerSocketChannel类型(TCP连接)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("Netty Client start successfully!");
/*
连接 服务器
并
等待 异步操作 执行完毕
*/
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
// 为 channelFuture 注册”监听器“
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("connect server successfully!");
} else {
System.out.println("connect server failure!");
}
}
});
/*
对 通道 关闭监听
并
等待 异步操作 执行完毕
*/
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 优雅停机
group.shutdownGracefully();
}
}
static class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("Hello Server, I am Client", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
/**
* 当通道有读取事件时会触发,即服务端发送数据给客户端
*
* @param ctx 上下文对象, 含有通道channel,管道pipeline
* @param msg 服务端 发送的数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("服务端的地址: " + ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
}
接下来,本人来展示下 运行结果:
运行结果:
首先是 服务端 的显示结果:
接下来是 客户端 的显示结果:
可以看到:
双端 通信都成功了!
在了解了 基本使用 之后,本人就来讲解下 Netty
的 模块组件 及其 作用:
模块组件:
请观看本人博文 —— 《【Netty】模块组件 详解》
生命周期:
请观看本人博文 —— 《【Netty】生命周期 详解》
接下来,本人来讲解下 Netty
的 编/解码:
编/解码:
请观看本人博文 —— 《【Netty】编/解码 详解》
接下来,本人来讲解下 Netty
的 粘包拆包:
粘包拆包:
请观看本人博文 —— 《【Netty】粘包/拆包 详解》
接下来,本人来讲解下 Netty
的 心跳机制:
心跳机制:
请观看本人博文 —— 《【Netty】心跳机制 详解》
接下来,本人来讲解下 Netty
的 断线自动重连:
断线自动重连:
请观看本人博文 —— 《【Netty】断线重连 详解》
接下来,本人来讲解下 Netty
的 核心源码:
由于 服务端 和 客户端 的 核心源码 类似,
因此,本人就来通过讲解 服务端 的 源码,来带同学们学习 Netty
的 架构思想:
核心源码:
请观看本人博文 —— 《【源码解析】Netty 服务端 详解》
最后,本人来扩展一个 在 开发 中,十分重要的知识点 —— Selector空轮询
的 解决
扩展 —— Selector空轮询 的 解决:
请观看本人博文 —— 《【Netty】Selector空轮询 解决 详解》
那么,看到这里,本专栏的博文,就讲解结束了!
Netty
在 Java届 是十分有名的!
可以这样讲:
Netty
在Java中间件
的 地位
相当于
Spring
在web开发届
的 地位
所以,我们如果想在 Java
上做 长期学习 以及 深造 的话,本人还是建议在 Netty
上多下些功夫!
秋招在即,希望和本人一样还在为了梦想拼搏的同学能够圆梦!