Hello Netty World
Hello Netty World
Netty 介绍
(官网)Netty is an asynchronous event-driven network application framework,
for rapid development of maintainable high performance protocol servers & clients.
(翻译)Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
先说结论,Netty 可以干什么:通常,我们使用 SpringBoot 搭建服务器,可以在网页上通过 URL 发出请求,服务器处理请求后返回响应,此时客户端和服务器是通过 HTTP 协议交换数据的;而使用 Netty,我们可以基于客户端和服务器之间的字节流,自定义编码解码规则(网络协议),从而实现自定义网络协议的应用程序。
再来说说 Netty 是什么:官网的介绍是,Netty 是一个网络应用程序框架(类似 SpringBoot 是 Web 应用程序的框架),即用于网络编程的一个框架。涉及到网络编程,就少不了网络编程底层的套接字(Socket),原生的 Socket 编程就不说了,Java 中已经提供了 NIO(Non-Blocking IO,非阻塞 IO)用于高性能的网络编程,那 NIO 和 Netty 有什么关系呢?再说一下结论:Netty 也是一种 NIO 框架,但对比 Java 原生的 NIO 和其他 NIO 框架,它更好使。
接着对比一下 NIO 和 Netty 的区别:
先看 NIO 的缺点:
- API 复杂,作为 Java 直接提供的 API,NIO 的 API 封装度较低,使用起来比较繁杂,学习成本高,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等对象;
- 使用 NIO 需要对多线程和网络编程较为了解,因为 NIO 涉及到 Reactor 模式(也叫 Dispatcher 模式,即 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程),没有良好的基础很难使用 NIO 写出健康的程序。
- NIO 还会有一些自带的 BUG,如 epoll bug,会导致 Selector 空轮询造成 CPU 占用 100%(最终是修复了,但影响力太大,臭名昭著)。
与之相比,Netty 就具有相对的优点:
- 底层封装了 NIO,API 简单,非常容易上手;
- 内置多种编码器解码器,支持多种协议,功能强大;
- 对比原生 NIO 和其他 NIO 框架,Netty 的性能是最好的;
- 社区活跃,使用者多,出现 BUG 的修复速度也非常快,质量有保证。
总之,对于构建应用来说,即使没有任何 NIO 相关知识,直接使用 Netty 也不会有任何负担。当然,最好的还是能了解底层的原理。下面通过构建一个 Netty 的 Hello World 程序,就可以感受到 Netty 的强大。
Hello Netty
服务端
从0开始,创建一个 Maven 的 Java 项目,引入 Netty 的依赖:
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
</dependencies>
然后,就可以直接开始构建服务器了,创建 MyServer 类,代码如下:
public class MyServer {
public static void main(String[] args) throws Exception {
// 创建两个线程组 boosGroup、workerGroup(步骤1)
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建服务端的启动对象 ServerBootstrap,设置启动参数(步骤2)
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置上面创建的线程组 boosGroup 和 workerGroup
bootstrap.group(bossGroup, workerGroup)
// 设置服务端的通道实现类型
.channel(NioServerSocketChannel.class)
// 设置线程队列的连接个数
.option(ChannelOption.SO_BACKLOG, 128)
// 设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 使用匿名内部类的形式初始化通道对象 并设置处理器(步骤3,处理器当然是自己写,自定义业务流程)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 为服务端 添加一个处理器
socketChannel.pipeline().addLast(new MyServerHandler());
}
});
System.out.println("Netty 服务端启动-Hello Server!");
// 绑定端口号 服务端启动(步骤4)
ChannelFuture channelFuture = bootstrap.bind(7777).sync();
// 监听关闭通道
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
简单归纳一下创建服务器的步骤:
- 创建两个线程组对象 bossGroup 和 workerGroup;
- 创建服务端的启动对象 ServerBootstrap,设置线程组、启动参数;
- 为服务端的通道设置处理器(顾名思义,此处就是自定义处理数据的地方了);
- 为服务端绑定端口,启动服务。
可见服务端的启动非常简单,步骤1、2、4可以说都是固定步骤,只有在步骤3中,需要添加处理数据的通道处理器,此处的 MyServerHandler 就是自定义的,代码如下:
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Read时触发
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到客户端" + ctx.channel().remoteAddress() + "发送的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// ReadComplete后触发,发送消息给客户端
ctx.writeAndFlush(Unpooled.copiedBuffer("服务端收到,你是" + ctx.channel().remoteAddress(), CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 发生异常关闭通道
ctx.close();
}
}
MyServerHandler 类继承了 ChannelInboundHandlerAdapter,它就是一个处理器了。其中,重写了 channelRead
等方法,这些方法都会在对应的场合被调用,如 channelRead
,对于接收到的数据 MyServerHandler 会调用 channelRead
方法处理数据,处理完成后又会调用 channelReadComplete
方法。
此处可以看到,channelRead
中读取了数据,在服务端打印了收到的数据(将数据转化为字节流后再转 Sting 读取),读取完后,又由 channelReadComplete
方法给客户端发送回复。
其中用到了 ChannelHandlerContext 对象,GPT 给出的介绍如下:
ChannelHandlerContext(通道处理上下文)是 Netty 中处理通道事件的核心接口,它封装了 Channel 和 ChannelHandler 之间的关联关系,以及 ChannelHandler 之间的交互操作。每当 Netty 中的通道(Channel)被激活时,会生成一个 ChannelHandlerContext 对象,并通过 ChannelPipeline 向通道处理器 ChannelHandler 传递事件和数据,让它们能够对通道进行处理。
简单来说,ChannelHandlerContext 提供了一种 Channel 和 ChannelHandler 之间进行通信和交互的方式,可以通过它来访问 Channel、调用 ChannelPipeline 中的其他处理器等。
客户端
完成了服务端,就到客户端了,与服务端类似,代码如下:
public class MyClient {
public static void main(String[] args) throws Exception {
// 创建 NIO 线程组(步骤1)
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
// 创建客户端的启动对象 Bootstrap,设置启动参数(步骤2)
Bootstrap bootstrap = new Bootstrap();
// 设置线程组
bootstrap.group(eventExecutors)
// 设置客户端的通道实现类型
.channel(NioSocketChannel.class)
// 使用匿名内部类初始化通道,并设置处理器(步骤3,处理器也是自定义的)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 为客户端 添加一个处理器
ch.pipeline().addLast(new MyClientHandler());
}
});
System.out.println("Netty 客户端到位-Hello Client!");
// 设置 IP 和 端口 连接服务端,操作系统会给客户端分配一个空闲端口号
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7777).sync();
// 对通道关闭进行监听
channelFuture.channel().closeFuture().sync();
} finally {
//关闭线程组
eventExecutors.shutdownGracefully();
}
}
}
简单归纳一下创建客户端的步骤:
- 创建 NIO 线程组对象 eventExecutors;
- 创建客户端的启动对象 Bootstrap,设置线程组、启动参数;
- 为客户端的通道设置处理器(与客户端系统,自定义的数据处理);
- 为客户端设置连接 IP 和端口,启动连接。
可以看到,创建客户端的流程与服务端差不多,只是参数不太一样。自定义的 MyClientHandler 代码如下:
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// Active时触发(连接时),发送消息到服务端
ctx.writeAndFlush(Unpooled.copiedBuffer("こんにちは、世界", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Read时触发
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到服务端" + ctx.channel().remoteAddress() + "的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
}
}
其中,channelActive
方法在连接时触发,向服务器发送消息;收到数据时调用 channelRead
处理数据,在客户端打印数据内容(字节流数据转 String)。
启动
完成了服务端和客户端后,就可以启动它们尝试一下了。首先启动服务端,直接运行 MyServer:

再运行 MyClient:

可以看到 MyClient 直接有输出内容了,这是因为启动连接成功后 MyClient 调用 channelActive
向 MyServer 发送了数据,MyServer 调用 channelRead
读取了数据,完成后又调用 channelReadComplete
向 MyClient 发送了数据,最后由 MyClient 的 channelRead
打印到了控制台上。
回到 MyServer 的控制台查看输出:

Hello Netty 程序完成,就是这么简单了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?