Chapter5 分隔符和定长解码器的应用

5.1 DelimiterBasedFrameDecoder应用开发

下面我们来完成一个演示程序使用DelimiterBasedFrameDecoder应用进行开发

5.1.1 DelimiterBasedFrameDecoder服务端开发

首先我们创建分隔符缓冲对象,本例中我们使用"$_"作为分隔符,然后创建DelimiterBasedFrameDecoder对象加入到ChannelPipeline中去

public class EchoServer {

    public void bind(int port) throws InterruptedException {
        // 配置服务端的NIO线程组
        // 主线程组, 用于接受客户端的连接,但是不做任何具体业务处理,像老板一样,负责接待客户,不具体服务客户
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (3)
                    .option(ChannelOption.SO_BACKLOG,100).handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch){
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });

            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync(); // (7)

            System.out.println("服务器启动完成,监听端口: " + port );

            // 等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8081;
        if (args != null && args.length > 0){
            try {
              port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e){

            }
        }
        new EchoServer().bind(port);
    }
}

public class EchoServerHandler extends ChannelInboundHandlerAdapter{

    int counter = 0;

    /**
     * 由于使用了DelimiterBasedFrameDecoder解码器,channelHandler接收到的msg就是一个完整的消息包
     * 然后StringDecoder将ByteBuf解码成了字符串对象
     * 所以这里的msg就是解码后的字符串对象
     * 我们在返回消息给客户端的时候加上"$_"分隔符 供客户端解码
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String body = (String) msg;
        System.out.println("This is " + ++counter + " times receive client : ["+body+"]");
        body+= "$_";
        ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
        ctx.writeAndFlush(echo);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
5.1.2 DelimiterBasedFrameDecoder客户端开发

与服务端类似,分别将DelimiterBasedFrameDecoder和StringDecoder添加到客户端ChannelPipeline中

public class EchoClient {
    public void connect(int port, String host) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            // 启动客户端
            ChannelFuture f = bootstrap.connect(host, port).sync();

            // 等待客户端链路关闭
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8081;
        if (args != null && args.length > 0){
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {

            }
        }

        new EchoClient().connect(port,"127.0.0.1");
    }
}

public class EchoClientHandler extends ChannelInboundHandlerAdapter{
    private int counter;

    private static final String ECHO_REQ = "Hi, Lilinfeng. Welcome to Netty.$_";

    public EchoClientHandler() {
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("This is " + ++counter + " times receive server : [" + msg + "]");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
5.1.3 运行DelimiterBasedFrameDecoder服务端和客户端

服务端成功接受了十条消息,客户端也成功接受了返回的10条消息,结果符合预期。

This is 1 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 2 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 3 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 4 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 5 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 6 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 7 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 8 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 9 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 10 times receive server : [Hi, Lilinfeng. Welcome to Netty.]

This is 1 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 2 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 3 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 4 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 5 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 6 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 7 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 8 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 9 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 10 times receive client : [Hi, Lilinfeng. Welcome to Netty.]

5.2 FixedLengthFrameDecoder 应用开发

FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码。

5.2.1 FixedLengthFrameDecoder 服务端开发

类似的,在ChannelPipeline中新增FixedLengthFrameDecoder解码器,长度设置为20

public class EchoServer {

    public void bind(int port) throws InterruptedException {
        // 配置服务端的NIO线程组
        // 主线程组, 用于接受客户端的连接,但是不做任何具体业务处理,像老板一样,负责接待客户,不具体服务客户
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (3)
                    .option(ChannelOption.SO_BACKLOG,100).handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch){
                            ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });

            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync(); // (7)

            System.out.println("服务器启动完成,监听端口: " + port );

            // 等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8081;
        if (args != null && args.length > 0){
            try {
              port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e){

            }
        }
        new EchoServer().bind(port);
    }
}

public class EchoServerHandler extends ChannelInboundHandlerAdapter{

    int counter = 0;

    /**
     * 由于使用了FixedLengthFrameDecoder解码器,无论一次接受了多少数据包,都会按照设置的定长解码,
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("This is " + ++counter + " times receive client : ["+msg+"]");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
5.2.2 利用telnet命令测试

如下所示,每次服务端读取数据为20字节定长

//终端工具命令
YaleWeideMacBook-Pro:~ yale$ telnet localhost 8081
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Lilinfeng welcome to Netty at Nanjing

//server 端接收打印
INFO: [id: 0x4a43fb90, L:/0:0:0:0:0:0:0:0:8081] READ COMPLETE
This is 1 times receive client : [Lilinfeng welcome to]

5.3 总结

DelimiterBasedFrameDecoder 用于对使用分隔符结尾的消息进行自动解码FixedLengthFrameDecoder用于对固定长度的消息进行自动解码。除了有上述两种解码器,再结合其他解码器,如字符串解码器等,可以轻松地完成对很多消息的自动解码,不用再考虑粘包拆包的读半包问题。

你可能感兴趣的:(Chapter5 分隔符和定长解码器的应用)