xiwenAndlejian / my-blog

Java基础学习练习题
1 stars 0 forks source link

Netty 学习笔记(二)EchoClient #16

Open xiwenAndlejian opened 5 years ago

xiwenAndlejian commented 5 years ago

Netty 学习笔记(二)EchoClient

本文内容:承接 #15 ,补全 EchoClient (回声客户端)的代码。 主要实现:

  1. 发送消息给服务端
  2. 接受服务端的响应
  3. 接受退出指令,客户端主动关闭与服务端的连接

源码


public class EchoClientDemo {

    private static final String EXIT_COMMAND = "exit()";

    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)// 注意此处使用的类与 EchoServer 中的不同
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<ByteBuf>() {
                                @Override
                                protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                                    System.out.println("接收到来自服务端的消息:" + msg.toString(CharsetUtil.UTF_8));
                                }
                            });
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8000);
            channelFuture.addListener(future -> {
                if (future.isSuccess()) {
                    System.out.println("连接服务端成功!");

                    startConsoleCommand(channelFuture.channel());
                } else {
                    System.err.println("连接服务端失败!");
                    future.cause().printStackTrace();
                }
            });
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }

    }

    private static void startConsoleCommand(Channel channel) {
        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String msg = scanner.nextLine();
                // 输入退出命令,关闭 channel
                if (EXIT_COMMAND.equals(msg)) {
                    System.out.println("接受退出指令,断开与服务端的连接。");
                    channel.close();
                    break;
                }
                ByteBuf byteBuf = channel.alloc().buffer();
                byteBuf.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
                channel.writeAndFlush(byteBuf);
            }
        }).start();
    }
}

进行测试

步骤:

  1. 启动 EchoServer
  2. 启动 EchoClient (可启动多个)
  3. 输入字符串(回车发送消息)
  4. 客户端主动断开连接

1. 启动 EchoServer

同 #15 ,此处略

2. 启动 EchoClient

启动结果如下图: image

3. 输入字符串(回车发送消息)& 4. 客户端主动断开连接

如下图: image

注:图中绿色的字是 Client 的输入。

源码解析

EchoClient 中有许多与 EchoServer 相同的源码,这里不再重复解析。 这里主要分析一下不同的代码:

  1. ChannelFuture.addListener(...)
  2. startConsoleCommand()

ChannelFuture.addListener

由于 Netty 中的 I/O 操作是异步的,即源码中的 connect()、writeAndFlush() 均是异步的。 查看源码可以发现,执行这两个操作都会返回一个相同的类型 ChannelFuture。实际的操作是不会立即执行的。只能通过返回的 ChannelFuture 来获取操作执行的状态,或者通过注册监听函数执行完成后的操作。 例如下方代码:

ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8000);
channelFuture.addListener(future -> {
    if (future.isSuccess()) {
        System.out.println("连接服务端成功!");

        startConsoleCommand(channelFuture.channel());
    } else {
        System.err.println("连接服务端失败!");
        future.cause().printStackTrace();
    }
});

startConsoleCommand


private static void startConsoleCommand(Channel channel) {
    new Thread(() -> {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String msg = scanner.nextLine();
            // 输入退出命令,关闭 channel
            if (EXIT_COMMAND.equals(msg)) {
                System.out.println("接受退出指令,断开与服务端的连接。");
                channel.close();
                break;
            }
            ByteBuf byteBuf = channel.alloc().buffer();
            byteBuf.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
            channel.writeAndFlush(byteBuf);
        }
    }).start();
}

在连接服务端成功后,启动此线程,接受控制台的输入,并发送给服务端。 这里需要注意的是,此处不能直接将控制台输入的 String 直接发送给服务端。即: channel.writeAndFlush(msg); 我们可以稍加修改代码,查看一下原因:


// 给 writeAndFlush() 操作增加监听函数
channel.writeAndFlush(msg).addListener(future -> {
    if (future.isSuccess()) {
        System.out.println("发送成功");
    } else {
        // 关键日志: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion)
        future.cause().printStackTrace();
    }
});

从错误信息可以看出,String 是不支持的消息类型,期望的是 ByteBuf/FileRegion。

总结

本文补全了上一篇文章 EchoServer 的 EchoClient,替换了 telnet 作为新的客户端。主要展示了如何通过代码创建 Client、连接 Server、接受&发送消息、Client 主动断开连接。 下一篇文章中,将详细解释 ByteBuf 以及它的用法。