Netty笔记第一个Netty程序

编程

源代码仓库 https://github.com/zhshuixian/netty-notes

这里将编写一个简单的 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 服务器。

以上是 Netty笔记第一个Netty程序 的全部内容, 来源链接: utcz.com/z/519021.html

回到顶部