【四】Netty 分隔符和定长解码器的应用

理论说明

TCP 以流的方式进行数据传输,长层的应用协议为了对消息进行区分,往往采用如下四种方式
(1)消息定长固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息:将计数器置位,重新开始读取下一个数据报
(2)将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛。
(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束标志。
(4)通过在消息头中定义长度字段来标识消息的总长度
Netty对上述四种应用都做了统一的抽象,提供了四种解码器来解决对应的问题,使用起来非常方便。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑粘包和拆包的问题。
【三】Netty 解决粘包和拆包问题 我们介绍了利用LineBasedFrameDecoder 解决了TCP的粘包问题。本节我们继续学习下另外两种实用的解码器 DelimiterBasedFrameDecoderFixedLengthFrameDecoder,前者可以自动完成以分隔符做结束标志的消息的解码,后者可以自动完成对定长消息的解码,它们都能解决TCP粘包和拆包导致的读半包问题。

LineBasedFrameDecoder 开发

大概流程

在这里插入图片描述
EchoServer接受到EchoClient的请求消息后,将其打印出来,然后将原始消息返回给客户端,消息以 $_ 作为分隔符.

代码展示

netty 依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha1</version>
        </dependency>

EchoServer 服务端启动类

public class EchoServer {

    public static void main(String[] args) {
        new EchoServer().bind(8080);
    }

    public void bind(int port) {
        //配置服务端的NIO线程组
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel)
                                throws Exception {
                            ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                            //添加 DelimiterBasedFrameDecoder 解码器,入参是对应的特殊字符 $_,作为分隔符
                            //1024 是单条消息的最大长度,当达到最大 长度也没有分隔符时就抛出异常
                            // 我们可以根据我们自己的需要,选择对应的特殊字符
                            socketChannel.pipeline().
                                    addLast(new
                                            DelimiterBasedFrameDecoder(
                                            1024, delimiter));
                            socketChannel.pipeline()
                                    .addLast(new StringDecoder());
                           /* socketChannel.pipeline()
                                    .addLast(new StringEncoder());*/
                            socketChannel.pipeline().addLast(
                                    new EchoServerHandler()
                            );
                        }
                    });
            //绑定端口,等待同步成功
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("server is started");
            //等待服务器监听端口关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
             //优雅退出
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

EchoServerHandler

public class EchoServerHandler extends ChannelHandlerAdapter {
    int counter=0;
    public void channelRead(ChannelHandlerContext context,Object body){
        //String body=(String)msg;
        // EchoServer 在 添加 childHandler中,添加了三个 ChannelHandler
        // 1.DelimiterBasedFrameDecoder 自动对请求消息进行解码,
        //    后续的ChannelHandler接受到的body就是完整的消息包
        // 2.StringDecoder 它将ByteBuf 解码成字符串对象
        // 3.EchoServerHandler 接受到的 body 对象就是解码后的字符串对象
        System.out.println("This is "+ ++counter+ " times receive client : 【"+body+"】");
        body+="$_";
        //由于我们设置  DelimiterBasedFrameDecoder 过滤掉了分隔符,所以,返回客户端时
        //需要将分隔符$_ 拼接上去,最后创建ByteBuf对象返回给客户端。
        ByteBuf echo= Unpooled.copiedBuffer(body.toString().getBytes());
        context.writeAndFlush(echo);

    }
}

EchoClient

public class EchoClient {
    public static void main(String[] args) {
        new EchoClient().connect("127.0.0.1",8080);
    }

    public void connect(String host,int port){
        //配置客户端NIO线程组
        EventLoopGroup loopGroup=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(loopGroup)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ByteBuf delimiter= Unpooled.copiedBuffer("$_".getBytes());
                            //添加 DelimiterBasedFrameDecoder 解码器,入参是对应的特殊字符 $_作为分隔符
                            //1024 是单条消息的最大长度,当达到最大 长度也没有分隔符时就抛出异常
                            // 我们可以根据我们自己的需要,选择对应的特殊字符
                            socketChannel.pipeline()
                                    .addLast(new DelimiterBasedFrameDecoder(
                                            1024,delimiter));
                            socketChannel.pipeline()
                                    .addLast(new StringDecoder());
                           /* socketChannel.pipeline()
                                    .addLast(new StringEncoder());*/
                            socketChannel.pipeline()
                                    .addLast(new EchoClientHandler());
                        }
                    });
            //进行tcp 链接服务端
            ChannelFuture future=bootstrap.connect(host,port).sync();
            System.out.println("client is started ....");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //优雅退出线程组
            loopGroup.shutdownGracefully();
         }
    }
}

EchoClientHandler

public class EchoClientHandler extends ChannelHandlerAdapter {
    private int counter;
    static final String ECHO_REQ = "Hi ,echo ,welcome to Netty $_";

    public void channelActive(ChannelHandlerContext context) {
        for (int i = 0; i < 10; i++) {
            context.writeAndFlush(
                    Unpooled.copiedBuffer(ECHO_REQ.getBytes())
            );
        }
    }

    public void channelRead(ChannelHandlerContext context,Object obj){
        System.out.println("the counter is "+ ++counter+" times receive server 【"+obj+" 】");
    }
}

结果打印

客户端打印

在这里插入图片描述

服务端打印

在这里插入图片描述

FixedLengthFrameDecoder 开发

FixedLengthFrameDecoder 是固定长度解码器,他能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP 粘包/拆包问题。
开发的代码比较简单,我们展示下代码:

代码展示

EchoServer 服务端启动类

public class EchoServer {

    public static void main(String[] args) {
        new EchoServer().bind(8080);
    }

    public void bind(int port) {
        //配置服务端的NIO线程组
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel)
                                throws Exception {
                            //定长解码器
                            socketChannel.pipeline().
                                    addLast(new
                                            FixedLengthFrameDecoder(
                                            20));
                            socketChannel.pipeline()
                                    .addLast(new StringDecoder());
                           /* socketChannel.pipeline()
                                    .addLast(new StringEncoder());*/
                            socketChannel.pipeline().addLast(
                                    new EchoFixServerHandler()
                            );
                        }
                    });
            //绑定端口,等待同步成功
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("server is started");
            //等待服务器监听端口关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
			//优雅退出
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

EchoFixServerHandler 业务处理类

public class EchoFixServerHandler extends ChannelHandlerAdapter {
    public void channelRead(ChannelHandlerContext context,Object msg){
        System.out.println("receive client message is 【"+msg+"】");
    }
}

客户端

我们采用NetAssist 网络助手来进行测试。
下载地址:https://download.csdn.net/download/echohuangshihuxue/87311946?spm=1001.2014.3001.5503
在这里插入图片描述

效果展示

服务端打印

在这里插入图片描述
由于我们配置的是定长20个字节,所以发送的内容分为三次来接受打印了。

总结

DelimiterBasedFrameDecoder 解码器用于对使用分隔符结尾的消息进行自动解码,
FixedLengthFrameDecoder 用于对固定长度的消息进行自动解码,有了上述两种解码器,再结合其他解码器,如字符串解码器,就可以轻松的完成对很多消息的自动解码,而且不再考虑TCP粘包和拆包导致的读半包的问题,极大的提升了开发效率。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值