《Netty》4.实现自己的EchoClient

Posted 申码er

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Netty》4.实现自己的EchoClient相关的知识,希望对你有一定的参考价值。

中我们已经实现了Server端,接下来编写EchoClient端。

编写Echo Client

步骤1.创建一个ChannelHandler,用来实现客户端从服务端接收到的数据的处理。
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /**
     * 在与服务器建立连接后调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //当客户端与服务端建立连接后,向服务端发送的内容
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    /**
     * 当从服务器接收到消息时调用,每当接收到数据时,就会调用此方法。
     * 服务器发送的消息可能是分批接收的,如果服务器发送了5个字节,就不能保证一次性接收到所有5个字节。
     * 即使是这样少量的数据,也可以调用channelRead0()方法两次,
     * 第一次使用ByteBuf (Netty的字节容器)保存3个字节,第二次使用ByteBuf保存2个字节。
     * 作为一种面向流的协议,TCP保证将按照服务器发送字节的顺序接收字节。
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
        //接收服务端向客户端发送的内容
        System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
    }

    /**
     * 在处理过程中引发异常时调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

疑问一:为什么使用SimpleChannelInboundHandler,而不是ChannelInboundHandlerAdapter?

SimpleChannelInboundHandler是ChannelInboundHandler的子类,关注于业务逻辑如何处理消息和Netty如何管理资源,当Client端channelRead0()完成时,代表当前已经获得到了消息,然后处理消息,当消息处理完成,方法返回时,SimpleChannelInboundHandler负责释放对保存消息的ByteBuf的内存引用(帮我们做了收尾工作,省事了)。

SimpleChannelInboundHandler#channelRead0()源码实现
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
 @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                //调用我们自己的实现
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                //释放内存引用
                ReferenceCountUtil.release(msg);
            }
        }
    }
}

疑问二:为什么在Server中不使用SimpleChannelInboundHandler?

在EchoServerHandler中,服务端需要将接收到的消息回显给客户端,假如客户端发送的消息体超过ByteBuf的大小,那么就要接收多次,当全部接收后,再发送给客户端,如果使用SimpleChannelInboundHandler会导致,当第一次接收到消息时,直接回显给客户端了,以致于客户端显示的消息不完整,由于这个原因,我们使用了ChannelInboundHandlerAdapter,它在channelRead时不释放消息,消息将在channelReadComplete()调用writeAndFlush()时中释放。

服务端EchoServerHandler的代码实现
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
 /**
     * 每一个接收到的消息
     *
     * @param ctx 上下文
     * @param msg 消息体
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        //打印服务端接收到的消息到控制台
        System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
        //将接收到的消息写入发送方,但不刷新出站消息
        ctx.write(in);
    }

 /**
     * 当最后一次调用channelRead()后,触发的后续操作
     *
     * @param ctx 上下文
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //将已经写入的消息刷新到客户端并关闭通道
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }
}

步骤2.创建客户端启动类:
public class EchoClient {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NiosocketChannel.class)
                    .remoteAddress(new InetSocketAddress("127.0.0.1", 9999))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}
客户端启动类代码与服务端启动类代码大致相同,具体作用也是一样的,就不过多介绍了,读者可以对比的注释来理解。

以上是关于《Netty》4.实现自己的EchoClient的主要内容,如果未能解决你的问题,请参考以下文章

以官方例子开启我们的netty源码之旅

netty源码解解析(4.0)-16 ChannelHandler概览

netty : websocketx.WebSocketHandshakeException: not a WebSocket handshake request: missing upgrade(代

EchoServer和EchoClient模型的改进1之多线程

Netty实战高性能分布式RPC(Dubbo分布式底层实现)

Netty网络编程实战4,使用Netty实现心跳检测机制