【Netty】粘包/拆包 详解

shadowLogo

在本篇博文中,本人要来讲解一个十分重要的问题 —— 粘包/拆包

首先,本人来讲解下 什么是 粘包拆包

定义:

TCP 是一个 流协议,就是 没有界限 的一长串 二进制数据

TCP 作为 传输层协议,并不了解上层业务数据的具体含义,
它会根据 TCP缓冲区实际情况 进行 数据包的划分

拆包:

业务 上认为是一个 完整的包,可能会被 TCP 拆分成 多个包 进行发送


粘包:

有可能把 多个小的包 封装成 一个大的数据包 发送,
这就是所谓的 TCP 粘包拆包 问题


面向 “流” 的通信是 无消息保护边界

那么,本人现在来给出一张图,来 展示下 粘包拆包

图示:

如下图所示,client发了 两个数据包 —— D1D2

但是 server端 可能会收到 如下几种情况 的 数据

粘包拆包
可以看到:

  • 第一行正常发送 情况
  • 第二行粘包 情况
  • 第三行拆包 情况
  • 第四行拆包 情况

现在,本人来展示下 使用Netty发送多条数据,出现 粘包拆包 现象

情况展示:

服务端:

package edu.youzg.demo.stick;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-06 16:37
 * @Description: 带你深究Java的本质!
 */
public class NettyServerDemo {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new ServerHandler());
                        }
                    });

            System.out.println("Netty Server start...");
            ChannelFuture channelFuture = bootstrap.bind(9000).sync();
            channelFuture.channel().closeFuture();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {

        }
    }

    static class ServerHandler extends SimpleChannelInboundHandler<String> {

        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
            System.out.println("====服务端接收到消息如下====");
            System.out.println("长度=" + msg.length());
            System.out.println("内容=" + msg);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

}

客户端:

package edu.youzg.demo.stick;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-06 16:37
 * @Description: 带你深究Java的本质!
 */
public class NettyClientDemo {

    public static void main(String[] args) {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new ClientHandler());
                        }
                    });

            System.out.println("Netty Client start...");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
            channelFuture.channel().closeFuture();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    static class ClientHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i = 0; i < 100; i++) {
                ctx.writeAndFlush("Youzg 是一个 good man!");
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

}

那么,本人来运行下,我们来看看 运行结果

运行结果:

粘包拆包

我们可以看到:

发生了 粘包拆包 现象


那么,我们该 如何解决 粘包拆包情况 呢?

解决方案:

自定义协议格式

  • 消息固定长度
    传输的数据大小 固定长度
    (例如:每段的长度固定为100字节,如果 不够空位补空格)
  • 特殊分隔符
    数据包尾部 添加 特殊分隔符
    (例如:下划线,中划线等,这种方法简单易行,但选择分隔符的时候一定要注意 每条数据的内部 一定不能出现 分隔符)
  • 发送 内容+长度
    发送每条 数据内容 的时候,将 数据的长度 一并发送
    (例如:可以选择 每条数据的前4位 是 数据的长度,应用层处理时可以 根据长度 来判断 每条数据 的 开始和结束)

Netty 也提供了多个 解码器,可以进行 分包 的操作:

Netty解码器:

解码器 功能
LineBasedFrameDecoder 回车换行 分包
DelimiterBasedFrameDecoder 特殊分隔符 分包
FixedLengthFrameDecoder 固定长度报文 分包

有关 Netty 提供的解码器,同学们可以在网上查找具体的使用方法
本人在这里来展示下 自定义协议格式处理实现

自定义协议格式 处理实现:

那么,本人现在来展示下 自定义协议格式 使用:

自定义协议:

package edu.youzg.demo.splite;

/**
 * 自定义协议 类型
 * @Author: Youzg
 * @CreateTime: 2021-05-06 11:08
 * @Description: 带你深究Java的本质!
 */
public class MessageProtocol {
    private int length;
    private byte[] content;

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

}

接下来,是 服务器

服务器:

package edu.youzg.demo.splite;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.CharsetUtil;

import java.util.List;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-06 10:07
 * @Description: 带你深究Java的本质!
 */
public class NettyServerDemo {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new MessageDecoder());
                            pipeline.addLast(new ServerHandler());
                        }
                    });

            System.out.println("Netty Server start...");
            ChannelFuture channelFuture = bootstrap.bind(9000).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    static class ServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {


        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
            System.out.println("====服务端接收到消息如下====");
            System.out.println("长度=" + messageProtocol.getLength());
            System.out.println("内容=" + new String(messageProtocol.getContent(), CharsetUtil.UTF_8));
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }

    }

    static class MessageDecoder extends ByteToMessageDecoder {

        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
            int length = 0;

            if (byteBuf.readableBytes() >= 4) {  // 当前缓冲区 包含 整个 数据长度信息
                length = byteBuf.readInt();
            }
            if (byteBuf.readableBytes() < length) { //当前缓冲区 未拥有 全部内容,继续等待
                System.out.println("waitting...");
                return;
            }
            byte[] content = new byte[length];
            if (byteBuf.readableBytes() >= length) {
                byteBuf.readBytes(content);

                MessageProtocol messageProtocol = new MessageProtocol();
                messageProtocol.setLength(length);
                messageProtocol.setContent(content);
                list.add(messageProtocol);
            }
        }
    }

}

接下来,是 客户端

客户端:

package edu.youzg.demo.splite;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.CharsetUtil;

/**
 * @Author: Youzg
 * @CreateTime: 2021-05-06 10:30
 * @Description: 带你深究Java的本质!
 */
public class NettyClientDemo {

    public static void main(String[] args) {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new MessageEncoder());
                            pipeline.addLast(new ClientHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();

            channelFuture.channel().closeFuture();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    static class ClientHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i = 0; i < 100; i++) {
                String msg = "Youzg 是一个 good man!";
                //创建 协议包对象
                MessageProtocol messageProtocol = new MessageProtocol();
                messageProtocol.setLength(msg.getBytes(CharsetUtil.UTF_8).length);
                messageProtocol.setContent(msg.getBytes(CharsetUtil.UTF_8));
                ctx.writeAndFlush(messageProtocol);
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }

    }

    static class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {

        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
            byteBuf.writeInt(messageProtocol.getLength());
            byteBuf.writeBytes(messageProtocol.getContent());
        }

    }

}

运行结果:

运行结果

可以看到:

没有发生 粘包拆包 情况!

posted @ 2021-05-06 17:09  在下右转,有何贵干  阅读(697)  评论(0编辑  收藏  举报