Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理

阅读本文建议从第一篇开始往后看

本系列文章

  • Netty在Android开发中的应用实战系列(一)——— 搭建服务端与客户端
  • Netty在Android开发中的应用实战系列(二)——— Encoder | Decoder | Handler 的使用
  • Netty在Android开发中的应用实战系列(三)——— 心跳处理 | 断线重连
  • Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理
  • Netty在Android开发中的应用实战系列(五)——— 创建Web服务 | 作为HTTP服务器

一、什么粘包呢?

简单来说就是:发送的多个包被粘到了一块变成了一个包。
比如说:服务端连续发送了两个包客户端确只收到了一条包(这一个包里其实就是两个数据包),正常来说客户端也应该收到两个包。这就是所谓的粘包 客户端遇到这种情况就需要做拆包处理了。

二、一般处理粘包的手段

  • 使用固定长度的数据
  • 使用特殊字符分割($、\n…)
  • 其他

Netty也为我们提供了几个处理粘包的解码器,如下:

  • DelimiterBasedFrameDecoder 基于特殊字符进行粘包拆包处理
  • FixedLengthFrameDecoder 基于固定长度进行粘包拆包处理
  • LengthFieldBasedFrameDecoder 基于消息头指定消息长度进行粘包拆包处理
  • LineBasedFrameDecoder 基于换行符( \r\n,\n)进行粘包拆包处理

三、下面通过一个示例来处理粘包拆包,这里我使用DelimiterBasedFrameDecoder 这个解码器然后使用$作为特殊分隔符

3.1 首先给服务端添加DelimiterBasedFrameDecoder

/**
 * 启动tcp服务端
 */
public void startServer() {
    try {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    //分隔符
                    ByteBuf delimiter = Unpooled.copiedBuffer("$".getBytes());
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //解决粘包
                        pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
                        //添加发送数据编码器
                        pipeline.addLast(new ServerEncoder());
                        //添加解码器,对收到的数据进行解码
                        pipeline.addLast(new ServerDecoder());
                        //添加数据处理
                        pipeline.addLast(new ServerHandler());
                    }
                });
        //服务器启动辅助类配置完成后,调用 bind 方法绑定监听端口,调用 sync 方法同步等待绑定操作完成
        b.bind(PORT).sync();
        handler.obtainMessage(0, "TCP 服务启动成功 PORT = " + PORT).sendToTarget();
        Log.d(TAG, "TCP 服务启动成功 PORT = " + PORT);
    } catch (Exception e) {
        e.printStackTrace();
    }

3.2 既然使用了特殊分隔符用来处理粘包,那么就需要给发送的每一个数据包添加上这个$符号;只需要在定义的Encoder中添加即可,如下:

public class ServerEncoder extends MessageToByteEncoder<PkgDataBean> {

    private static final String TAG = "ServerEncoder";

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, PkgDataBean data, ByteBuf byteBuf) throws Exception {
        //根据数据包协议,生成byte数组
        byte[] bytes = {0x2A, data.getCmd(), data.getDataLength()};
        byte[] dataBytes = data.getData().getBytes();
        //分隔符
        byte[] delimiter = "$".getBytes();
        //将所有数据合并成一个byte数组
        byte[] all = ByteUtil.byteMergerAll(bytes, dataBytes, new byte[]{0x2A}, delimiter);
        //发送数据
        byteBuf.writeBytes(all);
    }
}

3.3 我们写个连续发送数据包的代码

//获取与客户端的连接
List<ChannelHandlerContext> channels = ServerHandler.channels;
for (ChannelHandlerContext ctx : channels) {
    for (int i = 0; i < 3; i++) {
        PkgDataBean bean = new PkgDataBean();
        bean.setCmd((byte) 0x05);
        bean.setData("粘包的数据:" + i);
        bean.setDataLength((byte) bean.getData().getBytes().length);
        ctx.channel().writeAndFlush(bean);
    }
}
Log.d(TAG, "服务端发送了粘包数据");

四、既然服务端已经加了DelimiterBasedFrameDecoder解码器,那么客户端也是需要同步添加的;否则是无法正常解析数据的

public void connect() {
    try {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap()
                // 指定channel类型
                .channel(NioSocketChannel.class)
                // 指定EventLoopGroup
                .group(group)
                // 指定Handler
                .handler(new ChannelInitializer<SocketChannel>() {
                    //分隔符
                    ByteBuf delimiter = Unpooled.copiedBuffer("$".getBytes());
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new IdleStateHandler(10, 0, 0));
                        //解决粘包
                        pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
                        //添加发送数据编码器
                        pipeline.addLast(new ClientEncoder());
                        //添加收到的数据解码器
                        pipeline.addLast(new ClientDecoder());
                        //添加数据处理器
                        pipeline.addLast(new ClientHandler(NettyClient.this));
                    }
                });
        // 连接到服务端
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
        // 添加连接状态监听
        channelFuture.addListener(new ConnectListener(this));
        //获取连接通道
        channel = channelFuture.sync().channel();
        handler.obtainMessage(0, "连接成功").sendToTarget();
    } catch (Exception e) {
        handler.obtainMessage(0, "连接失败").sendToTarget();
        Log.e(TAG, "连接失败:" + e.getMessage());
        e.printStackTrace();
    }
}
  • 同样的,客户端发送的数据也需要在数据包的末尾写入$符号。

五、现在来看看程序执行的效果

  • 服务端连续发送3条数据
    Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理_第1张图片
  • 客户端收到的数据
    Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理_第2张图片

关于其他的拆包解码器大家可以根据自己的实际情况挑选一个合适的使用,到这里整个Netty在Android中的基本应用就讲的差不多了;相信你看完这四篇博客也可以在实战中使用Netty如鱼得水

本系列文章Demo下载地址

欢迎加入QQ群一起交流
在这里插入图片描述

你可能感兴趣的:(Android)