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

Posted 刘桂林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty在Android开发中的应用实战系列——— 粘包 拆包 处理相关的知识,希望对你有一定的参考价值。

本文来自阿钟的投稿,阅读大约8分钟

一、什么粘包呢?

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

二、一般处理粘包的手段

  • 使用固定长度的数据

  • 使用特殊字符分割($、\n….)

  • 其他

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

  • DelimiterBasedFrameDecoder 基于特殊字符进行粘包拆包处理

  • FixedLengthFrameDecoder 基于固定长度进行粘包拆包处理

  • LengthFieldBasedFrameDecoder 基于消息头指定消息长度进行粘包拆包处理

  • LineBasedFrameDecoder 基于换行符( \r\n,\n)进行粘包拆包处理

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

3.1 首先给服务端添加`DelimiterBasedFrameDecoder`

 1/**
2 * 启动tcp服务端
3 */

4public void startServer({
5    try {
6        EventLoopGroup bossGroup = new NioEventLoopGroup();
7        EventLoopGroup workerGroup = new NioEventLoopGroup();
8        ServerBootstrap b = new ServerBootstrap();
9        b.group(bossGroup, workerGroup)
10                .channel(NioserverSocketChannel.class)
11                .childHandler(new ChannelInitializer<SocketChannel>() {
12                    //分隔符
13                    ByteBuf delimiter = Unpooled.copiedBuffer("$".getBytes());
14                    @Override
15                    protected void initChannel(SocketChannel socketChannel) throws Exception 
{
16                        ChannelPipeline pipeline = socketChannel.pipeline();
17                        //解决粘包
18                        pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
19                        //添加发送数据编码器
20                        pipeline.addLast(new ServerEncoder());
21                        //添加解码器,对收到的数据进行解码
22                        pipeline.addLast(new ServerDecoder());
23                        //添加数据处理
24                        pipeline.addLast(new ServerHandler());
25                    }
26                });
27        //服务器启动辅助类配置完成后,调用 bind 方法绑定监听端口,调用 sync 方法同步等待绑定操作完成
28        b.bind(PORT).sync();
29        handler.obtainMessage(0"TCP 服务启动成功 PORT = " + PORT).sendToTarget();
30        Log.d(TAG, "TCP 服务启动成功 PORT = " + PORT);
31    } catch (Exception e) {
32        e.printStackTrace();
33    }

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

 1public class ServerEncoder extends MessageToByteEncoder<PkgDataBean{
2    private static final String TAG = "ServerEncoder";
3    @Override
4    protected void encode(ChannelHandlerContext channelHandlerContext, PkgDataBean data, ByteBuf byteBuf) throws Exception {
5        //根据数据包协议,生成byte数组
6        byte[] bytes = {0x2A, data.getCmd(), data.getDataLength()};
7        byte[] dataBytes = data.getData().getBytes();
8        //分隔符
9        byte[] delimiter = "$".getBytes();
10        //将所有数据合并成一个byte数组
11        byte[] all = ByteUtil.byteMergerAll(bytes, dataBytes, new byte[]{0x2A}, delimiter);
12        //发送数据
13        byteBuf.writeBytes(all);
14    }
15}

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

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

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

 1public void connect({
2    try {
3        NioEventLoopGroup group = new NioEventLoopGroup();
4        Bootstrap bootstrap = new Bootstrap()
5                // 指定channel类型
6                .channel(NioSocketChannel.class)
7                // 指定EventLoopGroup
8                .group(group)
9                // 指定Handler
10                .handler(new ChannelInitializer<SocketChannel>() {
11                    //分隔符
12                    ByteBuf delimiter = Unpooled.copiedBuffer("$".getBytes());
13                    @Override
14                    protected void initChannel(SocketChannel socketChannel) throws Exception 
{
15                        ChannelPipeline pipeline = socketChannel.pipeline();
16                        pipeline.addLast(new IdleStateHandler(1000));
17                        //解决粘包
18                        pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
19                        //添加发送数据编码器
20                        pipeline.addLast(new ClientEncoder());
21                        //添加收到的数据解码器
22                        pipeline.addLast(new ClientDecoder());
23                        //添加数据处理器
24                        pipeline.addLast(new ClientHandler(NettyClient.this));
25                    }
26                });
27        // 连接到服务端
28        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
29        // 添加连接状态监听
30        channelFuture.addListener(new ConnectListener(this));
31        //获取连接通道
32        channel = channelFuture.sync().channel();
33        handler.obtainMessage(0"连接成功").sendToTarget();
34    } catch (Exception e) {
35        handler.obtainMessage(0"连接失败").sendToTarget();
36        Log.e(TAG, "连接失败:" + e.getMessage());
37        e.printStackTrace();
38    }
39}
  • 同样的,客户端发送的数据也需要在数据包的末尾写入$符号。

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

服务端连续发送3条数据

图片

客户端收到的数据

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

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

Demo下载:

https://download.csdn.net/download/a_zhon/11799272

推荐我的慕课网Android实战课程,助你暴力提升Android技术。

https://coding.imooc.com/class/390.html

我创建了一个关于Android的交流群,有兴趣可以加我微信我拉你

图片

如果感觉现在的网络技术文章质量不高,苦于自己的Android技术无法得到明显的提升,感叹没有一帮好的学习伙伴及道友,那么我的知识星球可能就是一片净土,好的学习气氛,更好的技术资源与文章,自由且高效率,快来吧。

图片


点击阅读原文查看更多内容


以上是关于Netty在Android开发中的应用实战系列——— 粘包 拆包 处理的主要内容,如果未能解决你的问题,请参考以下文章

Netty网络编程实战2,使用Netty开发聊天室功能

Netty网络编程实战2,使用Netty开发聊天室功能

原创NIO框架入门:Android与MINA2Netty4的跨平台UDP双向通信实战

Netty实战(上)视频教程

Netty框架之协议应用二(RPC开发实战之Dubbo)

Netty框架之协议应用二(RPC开发实战之Dubbo)