前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty 是如何解决拆包和 粘包问题 ?最后一种方案最香

Netty 是如何解决拆包和 粘包问题 ?最后一种方案最香

作者头像
AI码师
发布2022-12-22 10:28:43
1.6K0
发布2022-12-22 10:28:43
举报

什么是拆包和粘包?

在TCP通信底层,当数据进入到缓冲区后,将数据进行组合发送,如果本次发送的数据超过了缓冲区的大小,那么就会将数据拆开,等到下次满足发送条件 再次发送给服务端,所以就会出现这几种情况:

情况一

这种属于正常情况,消息1和消息2分别发送

情况二

这种就是粘包现象,消息1和消息2在一个数据保中,一起发送到服务端,服务端如果不做特殊处理是区分不出来的

情况三

这种就属于拆包现象,消息1被拆成了两半,第一个数据包中只包含消息1的部分信息

演示拆包和粘包

代码见视频

有哪些方案可以解决拆包和粘包呢?

解决方案

方案一:固定长度

服务端和客户端定好每次发送的业务数据包长度,不够长度填充指定的字符,服务端每次按照固定长度进行读取

方案二:分隔符

同样,服务端和客户端定好业务数据分割符,服务端接受数据时,只有当收到分隔符时,才判断分隔符之前的数据是一个完整的业务数据,否则继续等待分隔符

方案三:自定义包结构体

每次发送数据的同时,将数据的长度也带上,但是存放数据长度的位置一定要定好,否则服务端无法识别哪个字节代表的是数据长度 那么 netty 针对这几种方案,自己是如何实现的呢?

netty解决方案

注意:所有的编解码器都要放在处理器之前

方案一:FixedLengthFrameDecoder 固定长度

代码语言:javascript
复制
try {
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup) //设置两个线程组
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 128)
            .childOption(ChannelOption.SO_KEEPALIVE, true)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.info("客户端channel 初始化");
                    ch.pipeline().addLast(new FixedLengthFrameDecoder(16));
                    ch.pipeline().addLast(new NettyServerHandler());
                }
            });

    ChannelFuture cf = bootstrap.bind(9999).sync();
    log.info("服务启动成功....");
    cf.addListener((ChannelFutureListener) future -> log.info("监听端口:{}",future.isSuccess()));
    cf.channel().closeFuture().sync();
} finally {
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

方案二:DelimiterBasedFrameDecoder/LineBasedFrameDecoder 分隔符

代码语言:javascript
复制
try {
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup) //设置两个线程组
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 128)
            .childOption(ChannelOption.SO_KEEPALIVE, true)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.info("客户端channel 初始化");
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    ch.pipeline().addLast(new NettyServerHandler());
                }
            });

    ChannelFuture cf = bootstrap.bind(9999).sync();
    log.info("服务启动成功....");
    cf.addListener((ChannelFutureListener) future -> log.info("监听端口:{}",future.isSuccess()));
    cf.channel().closeFuture().sync();
} finally {
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}
代码语言:javascript
复制
try {
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup) //设置两个线程组
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 128)
            .childOption(ChannelOption.SO_KEEPALIVE, true)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    log.info("客户端channel 初始化");
                    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.wrappedBuffer("lglbc".getBytes())));
                    ch.pipeline().addLast(new NettyServerHandler());
                }
            });

    ChannelFuture cf = bootstrap.bind(9999).sync();
    log.info("服务启动成功....");
    cf.addListener((ChannelFutureListener) future -> log.info("监听端口:{}",future.isSuccess()));
    cf.channel().closeFuture().sync();
} finally {
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

方案三:自定义包结构体

详细见视频

代码语言:javascript
复制
/**
 * @Description TODO
 * @Author 乐哥聊编程
 * @Date 2022/12/10 10:00
 */
@Slf4j
public class NettyMsgDecoder extends ByteToMessageDecoder {
    int length=0;
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes()>4){
            if(length==0){
                length=in.readInt();
            }
            if(in.readableBytes()<length){
                log.info("长度不够,继续等待");
                return;
            }
            byte[] content=new byte[length];
            in.readBytes(content);
            NettyMsg message=new NettyMsg();
            message.setLen(length);
            message.setContent(content);
            // 发送给下一个handler
            out.add(message);
            length=0;
        }
    }
}
代码语言:javascript
复制
/**
 * @Description TODO
 * @Author 乐哥聊编程
 * @Date 2022/12/10 10:00
 */
public class NettyMsgEncoder extends MessageToByteEncoder<NettyMsg> {
    @Override
    protected void encode(ChannelHandlerContext ctx, NettyMsg msg, ByteBuf out) throws Exception {
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 乐哥聊编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是拆包和粘包?
    • 情况一
      • 情况二
        • 情况三
        • 演示拆包和粘包
        • 有哪些方案可以解决拆包和粘包呢?
          • 解决方案
            • 方案一:固定长度
            • 方案二:分隔符
            • 方案三:自定义包结构体
          • netty解决方案
            • 方案一:FixedLengthFrameDecoder 固定长度
            • 方案二:DelimiterBasedFrameDecoder/LineBasedFrameDecoder 分隔符
            • 方案三:自定义包结构体
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档