Netty——TCP粘包和拆包


JAVA后端开发知识总结(持续更新…)


Netty——TCP粘包和拆包



一、概述

  对于TCP,发送端为了更高效地发送多个发给接收端的包,使用了优化方法——Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的。在接收端处理消息边界问题,就是粘包、拆包问题。

粘包拆包情况
在这里插入图片描述

二、TCP粘包演示

  • MyServerHandler
public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private int count = 0;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        // 将 buffer 转成字符串
        String message = new String(bytes, CharsetUtil.UTF_8);

        System.out.println("服务器接收到数据 " + message);
        System.out.println("服务器接收到消息量 = " + (++this.count));

        // 服务器回送数据到客户端,回送一个随机 Id
        ByteBuf response = Unpooled.copiedBuffer(UUID.randomUUID().toString() + "--", CharsetUtil.UTF_8);
        ctx.writeAndFlush(response);
    }
}
  • MyClientHandler
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private int count = 0;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 使用客户端发送 10 条数据,hello,server
        for (int i = 0; i < 10; i++) {
            String msg = "server" + i + "  ";
            System.out.println("发送消息 " + msg);
            ByteBuf byteBuf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8);
            ctx.writeAndFlush(byteBuf);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        // 将 buffer 转成字符串
        String message = new String(bytes, CharsetUtil.UTF_8);

        System.out.println("客户端接收到数据 " + message);
        System.out.println("客户端接收到消息量 = " + (++this.count));
    }
}
  • 结果

出现粘包情况

在这里插入图片描述

三、解决方案——自定义协议

  发送端每发送一次消息,就携带消息的长度,这样,接收方每次先接收消息的长度,再根据长度去读取该消息剩余的内容。

在这里插入图片描述

  • 自定义MessageProtocol对象
public class MessageProtocol {
    // 关键
    private int len;
    private byte[] content;
}
  • 将ByteBuf转换成MessageProtocol的解码器
public class MessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MessageDecoder 被调用");
        // 需要将获取到的二进制字节码转换成 MessageProtocol
        int length = in.readInt();
        byte[] content = new byte[length];
        in.readBytes(content);

        // 封装成 MessageProtocol 对象,放入 out,传递到下一个 Handler
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(length);
        messageProtocol.setContent(content);
        out.add(messageProtocol);
    }
}
  • 将MessageProtocol转换为ByteBuf的编码器
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        System.out.println("MessageEncoder 方法被调用");
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}
  • MyServerHandler
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    private int count = 0;
    // 接收的 Handler 继承了 SimpleChannelInboundHandler,以 MessageProtocol 的类型接收消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
        // 接收到数据,并处理
        int len = msg.getLen();
        byte[] content = msg.getContent();

        System.out.println("服务器第 " + (++count) +" 次接收到信息如下:");
        System.out.println("长度:" + len);
        System.out.println("内容:" + new String(content, CharsetUtil.UTF_8));

        // 回复消息
        String response = UUID.randomUUID().toString();
        int length = response.getBytes(CharsetUtil.UTF_8).length;
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(length);
        messageProtocol.setContent(response.getBytes());
        ctx.writeAndFlush(messageProtocol);
    }
}
  • MyClientHandler
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    private int count = 0;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 使用客户端发送 5 条数据,"今天天气冷,吃火锅" 编号
        for (int i = 0; i < 5; i++) {
            String message = "今天天气冷,吃火锅" + i;
            byte[] content = message.getBytes(CharsetUtil.UTF_8);
            int length = content.length;

            // 创建协议包对象
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLen(length);
            messageProtocol.setContent(content);
            ctx.writeAndFlush(messageProtocol);
        }
    }
}
  • 结果

在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值