Netty 笔记-第一个 Netty 程序
重磅干货,第一时间送达
源代码仓库:github.com/zhshuixian/netty-notes
博客:blog.csdn.net/u010974701
这里将编写一个简单的 Netty 程序 Ping-Pong(乒乓球) ,客户端向服务端发送一个信息,服务端将此信息返回给客户端。
这里 demo 项目使用 Maven,使用 Gradle 只需要引入相关依赖即可,如果网络的原因无法下载相关依赖,可以切换为国内的镜像源。
项目环境
-
IDEA 或者 Eclipse (IDE) -
Maven 或者 Gradle (构建工具) -
JDK 1.8 或者 11
1、第一个 Netty 程序
新建项目 01-ping-pong,引入如下依赖,以下分别为 Maven 和 Gradle 依赖配置。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
<version>${netty.version}</version>
<!-- 这里使用 4.1.50.Final -->
<!--Netty 4.1.30.Final及更新版本支持 JDK 11 -->
</dependency>
</dependencies>
</dependencyManagement>
// https://mvnrepository.com/artifact/io.netty/netty-all
compile group: 'io.netty', name: 'netty-all', version: '4.1.50.Final'
1.2、编写服务端
编写 Pong 服务器,需要实现如下内容,在项目的 server 包下:
-
ChannelHandler,编写服务器的业务逻辑,即对从客户端读取的数据并进行处理。 -
将服务器绑定到监听端口上,并指定 Handler 处理具体业务逻辑
ChannelHandler 负责接收并相应事件通知,Pong 服务器需要相应客户端传入的数据,所以需要实现 ChannelInboundHandler 接口用于响应客户端的请求。在这里使用 ChannelInboundHandlerAdapter ,它是 ChannelInboundHandler 默认实现。只需要继承其并覆写一些方法即可。
ChannelHandler 在 Netty 中分为了两类,入站事件处理和出站事件处理:
-
ChannelInboundHandler 是入站处理事件的接口,默认实现是 ChannelInboundHandlerAdapter -
ChannelOutboundHandler 是出站处理事件的接口,默认实现是 ChannelOutboundHandlerAdapter
代码:PongServerHandler.java
/**
* 接受和响应事件通知,实现具体的业务逻辑,ChannelHandler,ChannelHandler 是绑定在 ChannelPipeline 中,
* ChannelPipeline 可以绑定一个或者多个 ChannelHandler ,定义为经过 Channel 的入站和出站的一系列逻辑处理
* Chanel 建立的时候会绑定一个 ChannelPipeline 中,
* ChannelHandlerContext 是管理和关联 ChannelPipeline 中 ChannelHandler 之间的交互
*/
@Sharable // 表示可以被多个 Channel 共享
public class PongServerHandler extends ChannelInboundHandlerAdapter {
/** 当 Channel 中的数据读取成功后调用 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 从 ByteBuf 读取数据,msg 从 Channel 读取到的数据
ByteBuf in = (ByteBuf) msg;
System.out.println("Server Received: " + in.toString(CharsetUtil.UTF_8));
// 写入消息到 ChannelHandlerContext 中
ctx.write(in);
}
/** Channel 上一个读取操作完全完成后调用 */
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
// 将 ctx 中的数据写入并冲刷到远程节点,这是异步的
// 返回的是一个 ChannelFuture
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
/** 发生异常时候调用 */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 打印异常和关闭 ChannelHandlerContext
cause.printStackTrace();
ctx.close();
}
/** 新的客户端连接建立的时候会调用这个方法,可以不覆写 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// 从 ChannelHandlerContext 获取 Channel 的信息等
System.out.println("新建立的链接,客户端地址是 " + ctx.channel().remoteAddress());
}
}
代码解析:结合代码注释和下图,理解 ChannelHandlerContext、Channel 、ChannelHandler、ChannelPipeline 之间的关系,其中 ChannelHandler 包括了入站处理事件和出站处理事件。
上面的代码只是定义了一个 ChannelHandler,还需要 Bootstrap (引导)将 ChannelHandler 和 ChannelPipeline 组合起来,以及监听服务器端端口号。
代码:PongServer.java
public class PongServer {
private final int port;
public PongServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
new PongServer(port).start();
}
public void start() throws InterruptedException {
// Handler 处理的类实例化
final PongServerHandler serverHandler = new PongServerHandler();
// Netty 会为每个 Channel 分配一个 EventLoop,一个 EventLoop 可以分配给多个 Channel
// 这里使用 NIO 的 EventLoop
EventLoopGroup group = new NioEventLoopGroup();
try {
// 引导,功能是将 ChannelHandler,ChannelPipeline、EventLoop 组织起来,
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
// 使用 NIO 的 Channel
.channel(NioServerSocketChannel.class)
// 绑定的端口号
.localAddress(new InetSocketAddress(this.port))
// 绑定 Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
// 将 serverHandler 绑定到 ChannelPipeline
ch.pipeline().addLast(serverHandler);
}
});
// 每个 Netty 出站 I/O 操作返回 ChannelFuture
// 核心的bind()方法,用于监听端口和新建一个 ServerSocketChannel
ChannelFuture future = bootstrap.bind();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
代码解析:Netty 将 Selector 从应用程序中抽取出来,即为 EventLoop,使用 Netty 的时候无需自己手动实现 Selector 来处理 Channel。一个 EventLoop 对应的一个线程,EventLoop 主要的功能是:
-
将事件派发给 ChannelHandler -
注册 Channel,以及注册监听事件 -
负责 I/O 操作
https://www.cnblogs.com/leesf456/p/6902636.html EventLoop 的参考和扩展
Bootstrap(客户端) 和 ServerBootstrap(服务端) 是 Netty 的引导类,
-
服务端还需要指定监听端口、使用 bind() 方法开始监听 -
客户端需要指定服务端 IP 地址和服务端口号,使用 connect() 方法连接到服务端 -
两者相同的是绑定 ChannelPipeline 和 ChannelHandler 等一些组件
1.2、编写客户端
使用 Netty 编写 Ping 客户端跟服务端差不多,
-
ChannelHandler,编写客户端的业务逻辑,即向服务端发送和接受服务端返回的数据。 -
连接到服务器,并指定 Handler 处理具体业务逻辑
代码:PingClientHandler.java,代码跟 PongServerHandler.java 差不多
@Sharable
public class PingClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 使用 UTF-8 编码,ByteBuf Netty 的数据容器
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty!", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
String string = in.toString(CharsetUtil.UTF_8);
System.out.println("Client Received: " + string);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
代码:PingClient.java,代码解析见注释和 PongServer.java
public class PingClient {
public static void main(String[] args) throws InterruptedException {
new PingClient().start();
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
// 客户端使用 Bootstrap
Bootstrap bootstrap = new Bootstrap();
int port = 8080;
String host = "127.0.0.1";
// 通道使用 NioSocketChannel
bootstrap.group(group).channel(NioSocketChannel.class)
// 远程服务器路径和端口号
.remoteAddress(new InetSocketAddress(host, port))
// Channel 通道的绑定 ChannelPipeline
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 绑定 PingClientHandler
ch.pipeline().addLast(new PingClientHandler());
}
});
// 链接到服务端和使用 ChannelFuture 接收返回的数据
ChannelFuture future = bootstrap.connect().sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
1.3、运行项目
运行 PongServer 的 mian 方法后,运行 PingClient 的 mian 方法,在输出的窗口查看结果即可。服务端输出:
新建立的链接,客户端地址是 /127.0.0.1:3532
Server Received: Hello Netty!
客户端输出:
Client Received: Hello Netty!
通过以上的简单的 Ping-Pong (客户端-服务端)的通信,可以看出 Netty 比起原生 Java NIO 的 API 简洁的多,而且为了保证 Java NIO 的应用程序的正确性也不容易。
下一节,将使用 Netty 构建一个简单的 HTTP Web 服务器。