Netty入门程序
本章使用Netty开发一个入门程序,使用ServerBootstrap开发时间服务TimeServer,使用Bootstrap开发客户端TimeClient请求TimeServer获取时间。
开发 TimeServer之前,先回顾一下使用 NIO 进行服务端开发的步骤。
(1)创建 ServerSocketChannel,配置它为非阻塞模式;
(2)绑定监听,配置 TCP 参数,例如 backlog 大小;
(3)创建一个独立的 IO线程,用于轮询多路复用器 Seleetor;
(4)创建 Selector,将之前创建的 ServerSocketChannel 注册到 Selector 上, 监听SelectionKey.ACCEPT;
(5)启动IO线程,在循环体中执行 Selector.select()方法,轮询就绪的 Channel;
(6)当轮询到了处于就绪状态的 Channel 时,需要对其进行判浙,如果是 OP_ACCEPT状态,说明是新的客户端接入,则调用ServerSocketChannel.accept()方法接受新的客户端;
(7)设置新接入的客户端链路 SocketChannel 为非阻塞模式,配置其他的一些 TCP 参数;
(8)将 SocketChannel 注册到 Seleetor,监听 OP READ 操作位;
(9)如果轮询的 Channel 为 OP READ,则说明 SocketChannel 中有新的就绪的数据包需要读取,则构造 Byte Buffer 对象,读取数据包;
(10)如果轮询的 Channel 为 OP WRITE,说明还有数据没有发送完成,需要继续发送。
一、Netty入门程序
1.1 Netty开发服务端代码的流程:
1.创建EventLoopGroup EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); 2.初始化ServerBootstrap ServerBootstrap bootstrap = new ServerBootstrap(); 3.配置ServerBootstrap bootstrap.group(bossGroup, workerGroup); bootstrap.channel(NioServerSocketChannel.class); bootstrap.option(ChannelOption.SO_BACKLOG, 128); bootstrap.childHandler() 4.绑定端口并启动服务 try { ChannelFuture future = bootstrap.bind(PORT).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { ... } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } 在这个过程中,bind方法会同步阻塞,直到服务端绑定成功。当关闭future的channel时,表示服务端已关闭,这时可以安全地关闭EventLoopGroup,释放资源。
1.1.1 服务端完整代码
TimeServer
package Netty.Server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * Created by Idea 14 whih "netty" * * @Auhor: karl.zhao * @Email: karl.zhao@qq.com * @Date: 2015-11-28 * @Time: 15:54 */ public class TimeServer { public void bind(int port)throws Exception{ /* 配置服务端的NIO线程组 */ // NioEventLoopGroup类 是个线程组,包含一组NIO线程,用于网络事件的处理 // (实际上它就是Reactor线程组)。 // 创建的2个线程组,1个是服务端接收客户端的连接,另一个是进行SocketChannel的 // 网络读写 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup WorkerGroup = new NioEventLoopGroup(); try { // ServerBootstrap 类,是启动NIO服务器的辅助启动类 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 ChildChannelHandler extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel arg0)throws Exception{ arg0.pipeline().addLast(new TimeServerHandler()); } } 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 ex){} } new TimeServer().bind(port); } }
TimeServerHandler
package Netty.Server; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.Date; /** * Created by Idea 14 whih "netty" * * @Auhor: karl.zhao * @Email: karl.zhao@qq.com * @Date: 2015-11-28 * @Time: 16:13 */ public class TimeServerHandler extends ChannelHandlerAdapter { // 用于网络的读写操作 @Override public 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 order : " + body); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date( System.currentTimeMillis()).toString() : "BAD ORDER"; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); // 它的作用是把消息发送队列中的消息写入SocketChannel中发送给对方 // 为了防止频繁的唤醒Selector进行消息发送,Netty的write方法,并不直接将消息写入SocketChannel中 // 调用write方法只是把待发送的消息发到缓冲区中,再调用flush,将发送缓冲区中的消息 // 全部写到SocketChannel中。 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } }
1.2 Netty开发客户端代码的流程:
1.创建EventLoopGroup EventLoopGroup group = new NioEventLoopGroup(); 2.初始化Bootstrap Bootstrap bootstrap = new Bootstrap(); 3.配置Bootstrap bootstrap.group(group); bootstrap.channel(NioSocketChannel.class); bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); bootstrap.handler() 4.连接服务器 String host = "localhost"; int port = 8080; ChannelFuture future = bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { System.out.println("Connected to the server successfully."); } else { System.err.println("Failed to connect to the server."); future.cause().printStackTrace(); } }); // 等待连接成功或失败 future.syncUninterruptibly(); 5.关闭连接和EventLoopGroup Channel channel = future.channel(); ... channel.closeFuture().sync(); group.shutdownGracefully();
1.2.1 客户端完整代码
TimeClient
package Netty.Client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** * Created by Idea 14 whih "netty" * * @Auhor: karl.zhao * @Email: karl.zhao@qq.com * @Date: 2015-11-28 * @Time: 16:56 */ public class TimeClient { public void connect(String host, int port) throws Exception { // 配置服务端的NIO线程组 EventLoopGroup group = new NioEventLoopGroup(); try { // Bootstrap 类,是启动NIO服务器的辅助启动类 Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); // 发起异步连接操作 ChannelFuture f = b.connect(host, port).sync(); // 等待客服端链路关闭 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } 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 ex) { } } new TimeClient().connect("127.0.0.1", port); } }
TimeClientHandler
package Netty.Client; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; /** * Created by Idea 14 whih "netty" * * @Auhor: karl.zhao * @Email: karl.zhao@qq.com * @Date: 2015-11-28 * @Time: 16:58 */ public class TimeClientHandler extends ChannelHandlerAdapter { // 写日志 private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName()); private final ByteBuf firstMessage; public TimeClientHandler() { byte[] req = "QUERY TIME ORDER".getBytes(); firstMessage = Unpooled.buffer(req.length); firstMessage.writeBytes(req); } @Override public 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("Now is : " + body); } @Override public void channelActive(ChannelHandlerContext ctx) { // 当客户端和服务端建立tcp成功之后,Netty的NIO线程会调用channelActive // 发送查询时间的指令给服务端。 // 调用ChannelHandlerContext的writeAndFlush方法,将请求消息发送给服务端 // 当服务端应答时,channelRead方法被调用 ctx.writeAndFlush(firstMessage); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { logger.warning("message from:" + cause.getMessage()); ctx.close(); } }
1.3 运行结果
启动TimeServer,再启动TimeClient ,运行结果如下:
服务端:
客户端:
摘自: 李林峰《netty权威指南》
本文作者:MuXinu
本文链接:https://www.cnblogs.com/MuXinu/p/18091573
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步