Android 粘包与丢包处理工具类:支持多种粘包策略的 Helper 实现
在Android开发中,处理TCP/UDP通信时,粘包和丢包是常见的问题。粘包是指多个数据包被接收方一次性接收,导致数据包之间的界限不清晰;丢包则是指数据包在传输过程中丢失。为了处理这些问题,我们可以编写一个帮助类 PacketHelper,并提供不同的粘包处理策略。
功能概述
不处理粘包 :直接返回原始数据。
特定字符开始和结束 :通过指定的开始字符和结束字符标识数据包边界。
固定长度 : 每个数据包的长度固定。
可变长度 : 通过包头中的长度字段动态解析数据包长度。
从指定位置截取 :支持从数据包的指定位置开始截取到指定位置结束。
适用场景
TCP/UDP 通信中的数据粘包与丢包处理。
自定义协议的解析与数据包拆分。
需要灵活支持多种粘包策略的场景。
核心类与接口
PacketHandler 接口:定义统一的粘包处理接口。
NoOpPacketHandler:不处理粘包。
DelimitedPacketHandler:支持不同的开始字符和结束字符。
RangePacketHandler:支持从指定位置截取数据。
FixedLengthPacketHandler:处理固定长度的数据包。
VariableLengthPacketHandler:处理可变长度的数据包。
PacketHelper:封装粘包处理逻辑,提供统一调用接口。
1. 定义接口
首先,定义一个统一的接口 PacketHandler,用于处理不同的粘包策略:
public interface PacketHandler {
byte[] handlePacket(byte[] data);
}
2. 实现不同的粘包处理策略
2.1 不处理粘包
直接返回原始数据,不做任何处理:
public class NoOpPacketHandler implements PacketHandler {
@Override
public byte[] handlePacket(byte[] data) {
return data;
}
}
2.2 支持不同的开始字符和结束字符
通过指定的开始字符和结束字符来标识数据包的边界:
public class DelimitedPacketHandler implements PacketHandler {
private final byte[] startDelimiter; // 开始字符
private final byte[] endDelimiter; // 结束字符
public DelimitedPacketHandler(byte[] startDelimiter, byte[] endDelimiter) {
this.startDelimiter = startDelimiter;
this.endDelimiter = endDelimiter;
}
@Override
public byte[] handlePacket(byte[] data) {
int startIndex = indexOf(data, startDelimiter);
if (startIndex == -1) {
return null; // 没有找到开始字符
}
int endIndex = indexOf(data, endDelimiter, startIndex + startDelimiter.length);
if (endIndex == -1) {
return null; // 没有找到结束字符
}
// 返回从开始字符到结束字符之间的数据
return Arrays.copyOfRange(data, startIndex, endIndex + endDelimiter.length);
}
// 查找字节数组中指定模式的起始位置
private int indexOf(byte[] data, byte[] pattern) {
return indexOf(data, pattern, 0);
}
// 从指定位置开始查找字节数组中指定模式的起始位置
private int indexOf(byte[] data, byte[] pattern, int fromIndex) {
for (int i = fromIndex; i <= data.length - pattern.length; i++) {
boolean match = true;
for (int j = 0; j < pattern.length; j++) {
if (data[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match) {
return i;
}
}
return -1;
}
}
2.3 支持从指定位置到指定位置的截取
从数据包的指定位置开始截取,到指定位置结束
public class RangePacketHandler implements PacketHandler {
private final int startIndex; // 开始位置
private final int endIndex; // 结束位置
public RangePacketHandler(int startIndex, int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public byte[] handlePacket(byte[] data) {
if (data.length < endIndex) {
return null; // 数据长度不足
}
return Arrays.copyOfRange(data, startIndex, endIndex);
}
}
2.4 固定长度
每个数据包的长度是固定的:
public class FixedLengthPacketHandler implements PacketHandler {
private final int packetLength;
public FixedLengthPacketHandler(int packetLength) {
this.packetLength = packetLength;
}
@Override
public byte[] handlePacket(byte[] data) {
if (data.length >= packetLength) {
return Arrays.copyOfRange(data, 0, packetLength);
}
return null;
}
}
2.5 可变长度
数据包的长度是可变的,通过包头中的长度字段来确定:
public class VariableLengthPacketHandler implements PacketHandler {
private final int lengthFieldOffset;
private final int lengthFieldLength;
public VariableLengthPacketHandler(int lengthFieldOffset, int lengthFieldLength) {
this.lengthFieldOffset = lengthFieldOffset;
this.lengthFieldLength = lengthFieldLength;
}
@Override
public byte[] handlePacket(byte[] data) {
if (data.length >= lengthFieldOffset + lengthFieldLength) {
int packetLength = parseLength(data, lengthFieldOffset, lengthFieldLength);
if (data.length >= packetLength) {
return Arrays.copyOfRange(data, 0, packetLength);
}
}
return null;
}
private int parseLength(byte[] data, int offset, int length) {
int result = 0;
for (int i = 0; i < length; i++) {
result = (result << 8) | (data[offset + i] & 0xFF);
}
return result;
}
}
- 使用帮助类
PacketHelper 类用于封装粘包处理逻辑,提供统一的接口:
public class PacketHelper {
private PacketHandler packetHandler;
public PacketHelper(PacketHandler packetHandler) {
this.packetHandler = packetHandler;
}
public byte[] processPacket(byte[] data) {
return packetHandler.handlePacket(data);
}
}
- 示例用法
以下是使用不同粘包处理策略的示例:
public class Main {
public static void main(String[] args) {
// 示例数据
byte[] data = "STARTHelloWorldEND".getBytes();
// 1. 使用不同的开始字符和结束字符
PacketHelper helper1 = new PacketHelper(new DelimitedPacketHandler("START".getBytes(), "END".getBytes()));
byte[] packet1 = helper1.processPacket(data);
if (packet1 != null) {
System.out.println(new String(packet1)); // 输出: STARTHelloWorldEND
}
// 2. 使用从指定位置到指定位置的截取
PacketHelper helper2 = new PacketHelper(new RangePacketHandler(5, 10));
byte[] packet2 = helper2.processPacket(data);
if (packet2 != null) {
System.out.println(new String(packet2)); // 输出: Hello
}
// 3. 使用固定长度
PacketHelper helper3 = new PacketHelper(new FixedLengthPacketHandler(5));
byte[] packet3 = helper3.processPacket(data);
if (packet3 != null) {
System.out.println(new String(packet3)); // 输出: START
}
// 4. 使用可变长度
byte[] variableData = new byte[] {0x00, 0x05, 'H', 'e', 'l', 'l', 'o'};
PacketHelper helper4 = new PacketHelper(new VariableLengthPacketHandler(0, 2));
byte[] packet4 = helper4.processPacket(variableData);
if (packet4 != null) {
System.out.println(new String(packet4)); // 输出: Hello
}
}
}
5. 总结
重新整理后的代码结构清晰,功能完善,支持以下粘包处理策略:
不处理粘包:直接返回原始数据。
**不同的开始字符和结束字符:**通过 DelimitedPacketHandler 实现。
从指定位置到指定位置的截取:通过 RangePacketHandler 实现。
固定长度:通过 FixedLengthPacketHandler 实现。
**可变长度:**通过 VariableLengthPacketHandler 实现。
用户可以根据实际需求选择合适的策略,并通过 PacketHelper 统一调用。