02- 你的第一款Netty应用程序
本章主要内容:
-
设置开发环境(Java环境、Maven环境)
-
编写Echo服务器和客户端(简单的请求响应)
-
构建并测试应用
设置开发环境和测试在这里不多做概述,主要将如何应用Netty构建一个简单的请求响应客户端与服务端。
同样的这里也不对Netty的各个组件展开说明,后面在每一章节都有详细的解释,这一章的目的就是让你搭建一个简单的程序,让它能够跑起来。
2.2 Netty客户端/服务端概述
Echo 客户端和服务器之间的交互是非常简单的;在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然它本身看起来好像作用不大,但它充分地体现了客户端/服务器系统中典型的请求-响应交互模式。
2.3 编写Echo服务器
所有的Netty服务器都需要以下两部分。
-
至少一个ChannelHandler——该组件实现了服务器对客户端接收的数据处理,即它的业务逻辑。
-
引导——这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。
代码示例:
EchoServerHandler: @Sharable // 这是一个重点,标识一个ChannelHandler可以被多个Channel安全地共享,后面会展开阐述 public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ButeBuf) msg; // 将消息记录到控制台 System.out.println("Server received:" + in.toString(CharsetUtil.UTF-8)); // 将接收到的消息写给发送者,而不冲刷出站消息 ctx.write(in); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { // 将未决消息冲刷到远程节点,并且关闭该Channel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 打印异常栈跟踪、关闭该Channel cause.printStackTrace(); ctx.close(); } }
EchoServer: public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } // 校验端口号 public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage:" + EchoServer.class.getSimpleName() + "<port>"); return; } // 设置端口值(如果端口值参数的格式不正确,则抛出一个NumberFormatException) int port = Integer.parseInt(args[0]); // 调用服务器的start()方法 new EchoServer(port).start(); } public void start() throws Exception { final EchoServerHandler serverHandler = new EchoServerHandler(); // 创建EventLoopGroup EventLoopGroup group = new NioEventLoopGroup(); try { // 创建ServerBootstrap ServerBootstrap b = new ServerBootStrap(); b.group(group) .channel(NioServerScoketChannel.class) // 指定所使用的NIO传输Channel .localAddress(new InetSocketAddress(port)) // 使用指定的端口设置套接字地址 .childHandler(new ChannelInitializer<SocketChannel>() { // 添加一个EchoServerHandler到子Channel的ChannelPipeline @Override public void initChannel(SocketChannel ch) throws Exception { // EchoServerHandler被标注为@Shareable,所以我们总是可以使用同样的实例 ch.pipeline().addLast(serverHandler); } }); // 异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成 ChannelFuture f = b.bind().sync(); // 获取Channel的CloseFuture,并且阻塞当前线程直到它完成 } finally { // 关闭EventLoopGroup,释放所有资源 group.shutdownGracefully().sync(); } } }
2.4 编写Echo客户端
Echo客户端将会:
-
连接到服务器
-
发送一个或多个消息
-
对于每个消息,等待并接收从服务器发回的相同的消息
-
关闭连接
代码示例:
客户端的ChannelHandler: @Sharable // 标记该类的实例可以被多个Channel共享 public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override public void channelActive(ChannelHandlerContext ctx) { // 当被通知 Channel是活跃的时候,发送一条消息 ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!"), CharsetUtil.UTF_8); } @Override public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) { // 记录已接收消息的转储 System.out.println("Client receive:" + in.toString(CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throws cause) { // 发生异常时,记录错误并关闭Channel cause.printStackTrace(); ctx.close(); } }
EchoClient: public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.port = port; this.host = host; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect().sync(); f.channel().closeFuture().sync(); } finally { // 关闭线程池并释放所有资源 group.shutdownGracefully().sync(); } } } public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage:" + EchoClient.class.getSimpleName() + "<host><port>"); return; } String host = args[0]; int port = Integet.parseInt(args[1]); new EchoClient(host, port).start(); }
本章小结
在这一章你编写好了一个简单的Netty客户端与服务器,能够简单运行请求响应的程序。但是你一定留有很多疑问,但是在下面的章节中会详细解释Netty中每个组件的作用,并分析器底层的源代码,慢慢揭开它的神秘面纱。