Java 开发 - 粘包处理器 - 基于消息头 + 消息体(魔数验证、长度验证)

一、原始版本

  • DynamicLengthPacketHandler.java
java 复制代码
public class DynamicLengthPacketHandler {

    private byte[] buffer = new byte[0];
    private int parseFlag = PARSE_FLAG_HEADER;
    public static final int HEADER_LENGTH = 4;
    private int bodyLength;

    private static final int PARSE_FLAG_HEADER = 0;
    private static final int PARSE_FLAG_BODY = 1;

    public List<byte[]> handleData(byte[] data) {
        byte[] newBuffer = new byte[buffer.length + data.length];
        System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
        System.arraycopy(data, 0, newBuffer, buffer.length, data.length);
        buffer = newBuffer;

        List<byte[]> completeResults = new ArrayList<>();
        int position = 0;

        while (true) {
            if (parseFlag == PARSE_FLAG_HEADER) {

                // 处理消息头,得到消息体长度

                if (buffer.length - position < HEADER_LENGTH) {
                    break;
                }
                bodyLength = Byte.toUnsignedInt(buffer[position + 3]) |
                        (Byte.toUnsignedInt(buffer[position + 2]) << 8) |
                        (Byte.toUnsignedInt(buffer[position + 1]) << 16) |
                        (Byte.toUnsignedInt(buffer[position]) << 24);
                position += HEADER_LENGTH;
                parseFlag = PARSE_FLAG_BODY;
            }

            // 处理消息体

            if (position + bodyLength > buffer.length) {
                break;
            }
            byte[] completeResult = new byte[bodyLength];
            System.arraycopy(buffer, position, completeResult, 0, bodyLength);
            completeResults.add(completeResult);
            position += bodyLength;
            parseFlag = PARSE_FLAG_HEADER;
        }

        if (position > 0) {
            byte[] remaining = new byte[buffer.length - position];
            System.arraycopy(buffer, position, remaining, 0, remaining.length);
            buffer = remaining;
        }

        return completeResults;
    }

    public void clear() {
        buffer = new byte[0];
        parseFlag = PARSE_FLAG_HEADER;
    }

    public static byte[] buildPacket(String data) {
        byte[] body = data.getBytes();
        byte[] packet = new byte[4 + body.length];
        packet[0] = (byte) ((body.length >> 24) & 0xFF);
        packet[1] = (byte) ((body.length >> 16) & 0xFF);
        packet[2] = (byte) ((body.length >> 8) & 0xFF);
        packet[3] = (byte) (body.length & 0xFF);
        System.arraycopy(body, 0, packet, 4, body.length);
        return packet;
    }
}
  • 原始版本对异常长度完全没有处理能力,异常的长度包括错误的长度值、过长的长度值
  1. 错误的长度值:直接采用读取到的任何长度值,一旦解析到错误长度就卡住,无法继续处理后续数据

  2. 过长的长度值:例如,如果读取到过长的长度值,会尝试分配过大内存,导致内存溢出


二、原始版本测试用例

  1. 遇到错误的长度值
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0x00;
someData1[1] = (byte) 0x00;
someData1[2] = (byte) 0x00;
someData1[3] = (byte) 0x07;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

第 1 次解析结果数量: 0
第 2 次解析结果数量: 1
解析内容: Hello  
第 3 次解析结果数量: 0
  1. 遇到过长的长度值
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0x0F;
someData1[1] = (byte) 0xFF;
someData1[2] = (byte) 0xFF;
someData1[3] = (byte) 0xFF;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

第 1 次解析结果数量: 0
第 2 次解析结果数量: 0
第 3 次解析结果数量: 0
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0xFF;
someData1[1] = (byte) 0xFF;
someData1[2] = (byte) 0xFF;
someData1[3] = (byte) 0xFF;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

Exception in thread "main" java.lang.NegativeArraySizeException: -1

三、优化版本

  • DynamicLengthPacketHandler.java
java 复制代码
public class DynamicLengthPacketHandler {

    private byte[] buffer = new byte[0];

    public static final int HEADER_LENGTH = 8;

    private static final int MAGIC_NUMBER = 0x12345678;
    private static final byte MAGIC_BYTE_1 = (byte) 0x12;
    private static final byte MAGIC_BYTE_2 = (byte) 0x34;
    private static final byte MAGIC_BYTE_3 = (byte) 0x56;
    private static final byte MAGIC_BYTE_4 = (byte) 0x78;

    private int bodyLength;
    private static final int MAX_BODY_LENGTH = 20 * 1024 * 1024;
    private static final int MIN_BODY_LENGTH = 0;

    private int parseFlag = PARSE_FLAG_HEADER;
    private static final int PARSE_FLAG_HEADER = 0;
    private static final int PARSE_FLAG_BODY = 1;

    public List<byte[]> handleData(byte[] data) {
        byte[] newBuffer = new byte[buffer.length + data.length];
        System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
        System.arraycopy(data, 0, newBuffer, buffer.length, data.length);
        buffer = newBuffer;

        List<byte[]> completeResults = new ArrayList<>();
        int position = 0;

        while (true) {
            if (parseFlag == PARSE_FLAG_HEADER) {

                // 处理消息头

                if (buffer.length - position < HEADER_LENGTH) {
                    break;
                }

                int magic = Byte.toUnsignedInt(buffer[position + 3]) |
                        (Byte.toUnsignedInt(buffer[position + 2]) << 8) |
                        (Byte.toUnsignedInt(buffer[position + 1]) << 16) |
                        (Byte.toUnsignedInt(buffer[position]) << 24);
                if (magic != MAGIC_NUMBER) {
                    position++;
                    continue;
                }
                bodyLength = Byte.toUnsignedInt(buffer[position + 7]) |
                        (Byte.toUnsignedInt(buffer[position + 6]) << 8) |
                        (Byte.toUnsignedInt(buffer[position + 5]) << 16) |
                        (Byte.toUnsignedInt(buffer[position + 4]) << 24);
                if (bodyLength < MIN_BODY_LENGTH || bodyLength > MAX_BODY_LENGTH) {
                    position++;
                    continue;
                }

                position += HEADER_LENGTH;
                parseFlag = PARSE_FLAG_BODY;
            }

            // 处理消息体

            if (position + bodyLength > buffer.length) {
                break;
            }

            byte[] completeResult = new byte[bodyLength];
            System.arraycopy(buffer, position, completeResult, 0, bodyLength);
            completeResults.add(completeResult);

            position += bodyLength;
            parseFlag = PARSE_FLAG_HEADER;
        }

        if (position > 0) {
            byte[] remaining = new byte[buffer.length - position];
            System.arraycopy(buffer, position, remaining, 0, remaining.length);
            buffer = remaining;
        }

        return completeResults;
    }

    public void clear() {
        buffer = new byte[0];
        parseFlag = PARSE_FLAG_HEADER;
    }

    public static byte[] buildPacket(String data) {
        byte[] body = data.getBytes();
        byte[] packet = new byte[HEADER_LENGTH + body.length];

        // 设置魔数
        packet[0] = MAGIC_BYTE_1;
        packet[1] = MAGIC_BYTE_2;
        packet[2] = MAGIC_BYTE_3;
        packet[3] = MAGIC_BYTE_4;

        // 设置消息头
        packet[4] = (byte) ((body.length >> 24) & 0xFF);
        packet[5] = (byte) ((body.length >> 16) & 0xFF);
        packet[6] = (byte) ((body.length >> 8) & 0xFF);
        packet[7] = (byte) (body.length & 0xFF);

        // 设置消息体
        System.arraycopy(body, 0, packet, HEADER_LENGTH, body.length);

        return packet;
    }
}
  1. 优化版本通过魔数验证,只有正确的协议头才会解析长度,过滤掉垃圾数据

  2. 同时设置 MAX_BODY_LENGTH = 20MB,拒绝处理过大的包


四、优化版本测试用例

  1. 遇到错误的长度值
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0x00;
someData1[1] = (byte) 0x00;
someData1[2] = (byte) 0x00;
someData1[3] = (byte) 0x07;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

第 1 次解析结果数量: 0
第 2 次解析结果数量: 1
解析内容: World
第 3 次解析结果数量: 1
解析内容: Java
  1. 遇到过长的长度值
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0x0F;
someData1[1] = (byte) 0xFF;
someData1[2] = (byte) 0xFF;
someData1[3] = (byte) 0xFF;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

第 1 次解析结果数量: 0
第 2 次解析结果数量: 1
解析内容: World
第 3 次解析结果数量: 1
解析内容: Java
java 复制代码
byte[] someData1 = DynamicLengthPacketHandler.buildPacket("Hello");
byte[] someData2 = DynamicLengthPacketHandler.buildPacket("World");
byte[] someData3 = DynamicLengthPacketHandler.buildPacket("Java");

someData1[0] = (byte) 0xFF;
someData1[1] = (byte) 0xFF;
someData1[2] = (byte) 0xFF;
someData1[3] = (byte) 0xFF;

DynamicLengthPacketHandler handler = new DynamicLengthPacketHandler();

List<byte[]> results = handler.handleData(someData1);

System.out.println("第 1 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData2);

System.out.println("第 2 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}

results = handler.handleData(someData3);

System.out.println("第 3 次解析结果数量: " + results.size());
for (byte[] result : results) {
    System.out.println("解析内容: " + new String(result));
}
复制代码
# 输出结果

第 1 次解析结果数量: 0
第 2 次解析结果数量: 1
解析内容: World
第 3 次解析结果数量: 1
解析内容: Java
相关推荐
2301_800399722 小时前
stm32 printf重定向到USART
java·stm32·算法
csdn_wuwt2 小时前
有C#可用的开源的地图吗?
后端·c#·gis·map·开发·设计·地图
bagadesu2 小时前
15.<Spring Boot 日志>
java·后端
laplace01232 小时前
Maven
java·maven
小糖学代码2 小时前
网络:5.应用层协议HTTP
网络·网络协议·http
wdfk_prog2 小时前
Xshell终端连接Ubuntu/Debian无颜色的解决方案
java·ubuntu·debian
9ilk2 小时前
【基于one-loop-per-thread的高并发服务器】--- 项目测试
运维·服务器·c++·后端·中间件
fuze23332 小时前
解决在虚拟机的ensp中启动路由器,卡0%且出现虚拟机卡死的方法
网络·华为·ensp
艾迪的技术之路2 小时前
linux上gitlab runner部署文档
java·github