在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 {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 統一調用。