02- 你的第一款Netty应用程序

02- 你的第一款Netty应用程序

本章主要内容:

  • 设置开发环境(Java环境、Maven环境)

  • 编写Echo服务器和客户端(简单的请求响应)

  • 构建并测试应用

设置开发环境和测试在这里不多做概述,主要将如何应用Netty构建一个简单的请求响应客户端与服务端。

同样的这里也不对Netty的各个组件展开说明,后面在每一章节都有详细的解释,这一章的目的就是让你搭建一个简单的程序,让它能够跑起来。

 


2.2 Netty客户端/服务端概述

Echo 客户端和服务器之间的交互是非常简单的;在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然它本身看起来好像作用不大,但它充分地体现了客户端/服务器系统中典型的请求-响应交互模式

02- 你的第一款Netty应用程序

 


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客户端将会:

  1. 连接到服务器

  2. 发送一个或多个消息

  3. 对于每个消息,等待并接收从服务器发回的相同的消息

  4. 关闭连接

代码示例:

客户端的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中每个组件的作用,并分析器底层的源代码,慢慢揭开它的神秘面纱。

 

上一篇:全面理解String,netty入门到精通


下一篇:Netty精讲(二)编解码器、粘包拆包、心跳检测、零拷贝