3.2 Netty 服务端开发
TimeServer.javapublic class TimeServer {public void bind(int port) throws Exception {// 配置服务端的NIO线程组EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024).childHandler(new ChildChannelHandler());// 绑定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private class ChildChannelHandlerextends ChannelInitializer<SocketChannel>{@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new TimeServeHandler());}}public static void main(String[] args) throws Exception{int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {// 采用默认值}}new TimeServer().bind(port);}}
我 们 从 bind 方 法 开 始 学 习 , 在 代 码 第 5~6 行 创 建 了 两 个 NioEventLoopGroup 实 例 。NioEventLoopGroup 是 个 线 程 组 , 它 包 含 了 一 组 NIO 线 程 , 专 门 用 于 网 络 事 件 的 处 理 , 实际 上 它 们 就 是 Reactor 线 程 组 。 这 里 创 建 两 个 的 原 因 是 一 个 用 于 服 务 端 接 受 客 户 端 的 连 接 ,另 一 个 用 于 进 行 SocketChanneI 的 网 络 读 写 。 第 8行 创 建 ServerBootstrap 对 象 , 它 是 Netty用 于 启 动 NIO 服 务 端 的 辅 助 启 动 类 , 目 的 是 降 低 服 务 端 的 开 发 复 杂 度 。 第 9行 调 用ServerBootstrap 的 group 方 法 , 将 两 个 NIO 线 程 组 当 作 入 参 传 递 到 ServerBootstrap 中 。 接着 设 置 创 建 的 Channel 为 NioServerSocketChannel, 它 的 功 能 对 应 于 JDK NIO 类 库中的ServerSocketChannel 类 。 然 后 配 置 NioServerSocketCh annel 的 TC P 参 数 , 此 处 将 它 的 backlog
设 置 为 1024 , 最 后 绑 定 I/O 事 件 的 处 理 类 ChildChannelHandler , 它 的 作 用 类 似 于 Reactor模 式 中 的 Handler 类 , 主 要 用 于 处 理 网 络 1 / 0 事 件 , 例 如 记 录 日 志 、 对 消 息 进 行 编 解 码 等 。
服 务 端 启 动 辅 助 类 配 置 完 成 之 后 , 调 用 它 的 bind 方 法 绑 定 监 听 端 口 , 随 后 , 调 用 它的 同 步 阻 塞 方 法 sync 等 待 绑 定 操 作 完 成 。 完 成 之 后 Netty 会 返 回 一 个 ChannelFuture , 它的 功 能 类 似 于 JDK 的java.util.concurrent.Future
, 主 要 用 于 异 步 操 作 的 通 知 回 调 。
第 16行 使 用f.channel().closeFuture().sync()
方 法 进 行 阻 塞 , 等 待 服 务 端 鲢 路 关 闭 之 后main 函 数 才 退 出 。
第 19~20 行 调 用 NIO 线 程 组 的 shutdownGracefully 进 行 优 雅 退 出 , 它 会 释放跟shutdownGracefuIIy 相 关 联 的 资 源 。
TimeServeHandler.java
public class TimeServeHandler extends ChannelHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req,"UTF-8");System.out.println("The time server receive order : " + body);String currentTime ="QUERY TIME ORDER".equalsIgnoreCase(body)? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());ctx.write(resp);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}}
第 5 行 做 类 型 转 换 , 将 msg 转 换 成 Netty 的 ByteBuf 对 象 。 ByteBuf 类 似 于 JDK 中 的java.nio.ByteBuffer 对 象 , 不 过 它 提 供 了 更 加 强 大 和 灵 活 的 功 能 。 通 过 ByteBuf 的readabl e Bytes 方 法 可 以 获 取 缓 冲 区 可 读 的 字 节 数 , 根 据 可 读 的 字 节 数 创 建 byte 数 组 , 通 过ByteBuf 的 read Bytes 方 法 将 缓 冲 区 中 的 字 节 数 组 复 制 到 新 建 的 byte 数 组 中 , 最 后 通 过 new
string 构 造 函 数 获 取 请 求 消 息 。 这 时 对 请 求 消 息 进 行 判 断 , 如 果 是 "QUERY TIME ORDER"则 创 建 应 答 消 息 , 通 过 ChanneIHandIerContext 的 write 方 法 异 步 发 送 应 答 消 息 给 客 户 端 。
第 20 行 我 们 发 现 还 调 用 了ChannelHandlerContext
的 flush 方 法 , 它 的 作 用 是 将 消 息发 送 队 列 中 的 消 息 写 入 到 SocketChannel 中 发 送 给 对 方 。 从 性 能 角 度 考 虑 , 为 了 防 止 频 繁地 唤 醒 SeIector 进 行 消 息 发 送 , Netty 的 write 方 法 并 不 直 接 将 消 息 写 入 SocketChanneI 中 ,调 用 write 方 法 只 是 把 待 发 送 的 消 息 放 到 发 送 缓 冲 数 组 中 , 再 通 过 调 用 flush 方 法 , 将 发 送缓 冲 区 中 的 消 息 全 部 写 到 SocketChannel 中 。
第 25 行 , 当 发 生 异 常 时 , 关 闭 ChanneIHandIerContext , 释 放 和 ChanneIHandIerContext相 关 联 的 句 柄 等 资 源 。通 过 对 代 码 进 行 统 计 分 析 可 以 看 出 , 不 到 30 行 的 业 务 逻 辑 代 码 , 即 完 成 了 NIO 服 务端 的 丌 发 , 相 比 于 传 统 基 于 JDK NIO 原 生 类 库 的 服 务 端 , 代 码 量 大 大 减 少 , 开发难 度 也降 低 了 很 多 。
3.3 Netty 客户端开发
TimeClient.javapublic class TimeClient {public void connect(int port, String host) throws Exception {// 配置客户端NIO 线程组EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new TimeClientHandler());}});// 发起异步连接操作ChannelFuture f = b.connect(host, port).sync();// 当代客户端链路关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放NIO线程组group.shutdownGracefully();}}/*** @param args* @throws Exception*/public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {// 采用默认值}}new TimeClient().connect(port, "127.0.0.1");}}
我 们 从 connect 方 法 讲 起 , 在 第 5行 首 先 创 建 客 户 端 处 理 I/O 读 写 的 NioEventLoopGroup 线 程 组 , 然 后 继 续 创 建 客 户 端 辅 助 启 动 类 Bootstrap , 随 后 需 要 对 其 进 行 配 置 。 与 服务 端 不 同 的 是 , 它 的 ChanneI 需 要 设 置 为 N ioSocketChanneI , 然 后 为 其 添 加 HandIer. 此 处为 了 简 单 直 接 创 建 匿 名 内 部 类 , 实 现 initChannel 方 法 , 其 作 用 是 当 创 建 NioSocketChanneI
成 功 之 后 , 在 进 行 初 始 化 时 , 将 它 的 ChannelHandler 设 置 到 ChannelPipeline 中 , 用 于 处 理网 络 I/O 事 件 。
客 户 端 启 动 辅 助 类 设 置 完 成 之 后 , 调 用 connect 方 法 发 起 异 步 连 接 , 然 后 调 用 同 步 方法 等 待 连 接 成 功 。
最 后 , 当 客 户 端 连 接 关 闭 之 后 , 客 户 端 主 函 数 退 出 , 退 出 之 前 释 放 NIO 线 程 组 的 资 源 。
TimeClientHandler.java
public class TimeClientHandler extends ChannelHandlerAdapter {private static final Logger logger =Logger.getLogger(TimeClientHandler.class.getName());private final ByteBuf firstMessage;// Creates a client-side handler.public TimeClientHandler() {byte[] req = "QUERY TIME ORDER".getBytes();firstMessage = Unpooled.buffer(req.length);firstMessage.writeBytes(req);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(firstMessage);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req,"UTF-8");}}
这 里 重 点 关 注 三 个 方 法 : channelActive 、 channelRead 和 exceptionCaught0 当 客 户 端和 服 务 端 TCP 链 路 建 立 成 功 之 后 , Netty 的 NIO 线 程 会 调 用 channelActive 方 法 , 发 送 查询 时 间 的 指 令 给 服 务 端 , 调 用 ChannelHandlerContext 的 writeAndFlush 方 法 将 请 求 消 息 发送 给 服 务 端 。
当 服 务 端 返 回 应 答 消 息 时 , channelRead 方 法 被 调 用 , 第 39 ~ 43 行 从 Netty 的 ByteBuf中 读 取 并 打 印 应 答 消 息 。
第 21~26 行 , 当 发 生 异 常 时 , 打 印 异 常 日 志 , 释 放 客 户 端 资 源 。