Java 开发 - 粘包处理器 - 基于消息头 + 消息体(魔数验证、长度验证)
一、原始版本
- DynamicLengthPacketHandler.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;}
}
- 原始版本对异常长度完全没有处理能力,异常的长度包括错误的长度值、过长的长度值
-
错误的长度值:直接采用读取到的任何长度值,一旦解析到错误长度就卡住,无法继续处理后续数据
-
过长的长度值:例如,如果读取到过长的长度值,会尝试分配过大内存,导致内存溢出
二、原始版本测试用例
- 遇到错误的长度值
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
- 遇到过长的长度值
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
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
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;}
}
-
优化版本通过魔数验证,只有正确的协议头才会解析长度,过滤掉垃圾数据
-
同时设置
MAX_BODY_LENGTH = 20MB,拒绝处理过大的包
四、优化版本测试用例
- 遇到错误的长度值
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
- 遇到过长的长度值
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
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
