Netty在Android開發中的應用實戰系列(四)——— 粘包 | 拆包 處理

閱讀本文建議從第一篇開始往後看

本系列文章

一、什麼粘包呢?

簡單來說就是:發送的多個包被粘到了一塊變成了一個包。
比如說:服務端連續發送了兩個包客戶端確只收到了一條包(這一個包裏其實就是兩個數據包),正常來說客戶端也應該收到兩個包。這就是所謂的粘包 客戶端遇到這種情況就需要做拆包處理了。

二、一般處理粘包的手段

  • 使用固定長度的數據
  • 使用特殊字符分割($、\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中的基本應用就講的差不多了;相信你看完這四篇博客也可以在實戰中使用Netty如魚得水

本系列文章Demo下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章