Netty框架之TCP粘包/半包解决方案


谈到TCP粘包/半包的解决方案,我们不妨先认识造成TCP粘包/半包的原因有哪些,以便于更深刻理解解决方案的原理

一.TCP粘包

1.现象:发送:abc,def,接收:abcdef
2.原因:

  • 应用层:接受数据的Bytebuf缓冲区设置太大(Netty默认为1024)存储了多个TCP包,一次性取的时候取出缓冲区所有数据,导致多个包粘在一起.
  • 滑动窗口:发送方发送消息太快,接收方接收消息也快,但是处理消息太慢,并且接收窗口足够大,那么此时消息就会堆积在接收窗口,那么就会导致多个包粘在一起
  • Nagle算法:Nagle算法是操作系统底层自动实现的,为了减少发送次数,提高效率,操作系统会在缓冲区未满的时候等待消息填充,直到缓冲区满的时候一并发送,造成粘包

二.TCP半包

1.现象:发送:abcdef,接收:abc,def
2.原因:

  • 应用层:接受数据的Bytebuf缓冲区设置太小(Netty默认为1024)存储不了一个完整的TCP包,一次性只能取出TCP包的部分数据
  • 滑动窗口:发送方发送窗口较大,接收方接收窗口较小,导致接收方只能接收TCP包的部分数据,导致TCP半包
  • MSS限制:为了限制TCP包的大小,会将大的TCP包分组成多个小的TCP包发送,导致半包问题

三.TCP粘包/半包解决方案

1.FixedLengthFrameDecoder定长解析器

向pipeline中加入FixedLengthFrameDecoder(int length)定长解析器,取出指定字节长度的数据作为完成数据,不过这要求完整数据的长度是定长

使用方法,请尝试理解下面源码

public class FixedLengthFrameDecoderTest {
    public static ByteBuf Message(){

        StringBuilder sb = new StringBuilder();
        char c = 'a';
        for(int i=0;i<20;i++){
            sb.append(c);
            c++;
        }
        return Unpooled.copiedBuffer(sb.toString(),StandardCharsets.UTF_8);
    }
    public static void main(String[] args) {
        EmbeddedChannel channel = new EmbeddedChannel(
                //定长解析器,取出指定字节长度的数据
                new FixedLengthFrameDecoder(10),
                new SimpleChannelInboundHandler<ByteBuf>() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf buf) throws Exception {
                        //将Bytebuf的信息读取到字节数组
                        int len = buf.readableBytes();
                        byte[] msg = new byte[len];
                        buf.readBytes(msg);
                        //把字节数组转化为String输出
                        System.out.println(new String(msg,0,len));
                    }
                }
        );
        //模拟入栈,向服务器发送消息
        channel.writeInbound(Message());
    }
}

2.LineBasedFrameDecoder行解析器

向pipeline中加入LineBasedFrameDecoder行解析器,会以换行符"\n"作为完整消息边界,不过着要求每一个完整消息的末尾都加上换行符作为

使用方法,请尝试理解下面源码

public class LineBasedFrameDecoderTes {
    //构造一个消息
    public static ByteBuf Massage(){
        StringBuilder msg = new StringBuilder();
        char c = 'a';
        for(int i=0;i<10;i++){
            msg.append(c);
            c++;
        }
        //加一个换行符,作为结束符
        msg.append("\n");

        //重复一次
        for(int i=0;i<10;i++){
            msg.append(c);
            c++;
        }
        //加一个换行符,作为结束符
        msg.append("\n");

        return Unpooled.copiedBuffer(msg.toString(), StandardCharsets.UTF_8);
    }
    public static void main(String[] args) {
        //网络通信测试类,可以模拟客户端向服务器发送消息,不用实现客户端和服务端,方便调试所用
        EmbeddedChannel channel = new EmbeddedChannel(
                //最大长度设置,如果到了1024还没有遇到结束符,那么会报错
                new LineBasedFrameDecoder(10),
                new SimpleChannelInboundHandler<ByteBuf>(){

                    @Override
                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf buf) throws Exception {
                        //接收到消息,做输出处理
                        int len = buf.readableBytes();
                        byte[] msg = new byte[len];
                        //Bytebuf中的数据读取到byte字节数组
                        buf.readBytes(msg);

                        //byte字节数组转换为String字符串并输出
                        System.out.println(new String(msg,0,len));
                    }
                }
        );

        channel.writeInbound(Massage());

    }
}

3.LengthFieldBasedFrameDecoder

向pipeline中加入LineBasedFrameDecoder行解析器,以设置长度读取消息
,不过这要求数据以消息长度+消息体的形式发送,也即先告知服务器数据的长度,再发送完整的数据

参数设置:

1.maxFrameLength:消息最大字节长度
2.lengthFieldOffset:声明消息长度变量的偏移量(也即字节下标是多少)
3.lengthFieldLength:声明消息长度变量的字节大小(一般用int声明,4字节)
4.lengthAdjustment:调着消息体的其实位置,如果忽略一个字节就设置为1,不忽略设置为0
5.initialBytesToStrip:最终消息从头剥离几个字节

使用方法,请尝试理解下面源码

/**
 * LTC解码器要求:
 * 消息长度(int)+消息体一起发送
 */
public class LengFiledBasedFrameDecoderTest {
    public static ByteBuf Message(){
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        StringBuilder sb = new StringBuilder();
        char c = 'a';
        for(int i=0;i<20;i++){
            sb.append(c);
            c++;
        }
        byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
        buf.writeInt(bytes.length);
        buf.writeBytes(bytes);
        return buf;
    }
    public static void main(String[] args) {
        EmbeddedChannel channel = new EmbeddedChannel(
                /*参数设置
                1.maxFrameLength:消息最大字节长度
                2.lengthFieldOffset:声明消息长度变量的偏移量(也即字节下标是多少)
                3.lengthFieldLength:声明消息长度变量的字节大小(一般用int声明,4字节)
                4.lengthAdjustment:调着消息体的其实位置,如果忽略一个字节就设置为1,不忽略设置为0
                5.initialBytesToStrip:最终消息从头剥离几个字节
                *
                */
                new LengthFieldBasedFrameDecoder(1024,0,4,0,4),
                new SimpleChannelInboundHandler<ByteBuf>(){

                    @Override
                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf buf) throws Exception {
                        int len = buf.readableBytes();
                        byte[] msg = new byte[len];
                        buf.readBytes(msg);
                        System.out.println(new String(msg,0,len));
                    }
                }
        );
        channel.writeInbound(Message());
    }

}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值