UDP 不保證順序、不保證送達、不保證不重複。 先發的包可能後到,甚至丟失、重複。網絡層面無任何順序機制。
// 簡單協議頭結構 (C/C++ 風格)
#pragma pack(push, 1)
struct UdpPacketHeader {
uint32_t seq; // 遞增序號,從 0 開始
uint16_t total_len; // 整包總長度(含頭)
uint16_t frag_idx; // 分片索引(大包分片時使用)
uint16_t frag_cnt; // 總分片數
uint32_t crc32; // 可選校驗
// payload 緊隨其後
};
#pragma pack(pop)
std::map<uint32_t, Packet> 存儲亂序到達的包。next_expected_seq,初始 0。seq。seq == next_expected_seq → 直接交付上層,next_expected_seq++,檢查緩存區是否有後續連續包,循環交付。seq > next_expected_seq → 存入緩存區(去重)。seq < next_expected_seq → 重複包,丟棄。MAX_WAIT_MS 仍未收到期望包 → 判定丟包,觸發 NACK 請求重發或上層錯誤處理。public class UdpSequencer {
private final Map buffer = new TreeMap<>();
private int expectedSeq = 0;
private final int windowSize = 1024;
public void onPacketReceived(DatagramPacket pkt) {
ByteBuffer bb = ByteBuffer.wrap(pkt.getData(), pkt.getOffset(), pkt.getLength());
int seq = bb.getInt();
byte[] payload = new byte[bb.remaining()];
bb.get(payload);
if (seq == expectedSeq) {
deliver(payload);
expectedSeq++;
drainBuffer();
} else if (seq > expectedSeq && seq < expectedSeq + windowSize) {
buffer.putIfAbsent(seq, payload); // 去重
}
// else: 重複或過舊,忽略
}
private void drainBuffer() {
while (buffer.containsKey(expectedSeq)) {
deliver(buffer.remove(expectedSeq));
expectedSeq++;
}
}
private void deliver(byte[] data) {
// 交給業務層處理
}
}
| 特性 | TCP | UDP + 應用層序列化 |
|---|---|---|
| 順序保證 | 內核自動 | 應用層實現 |
| 延遲特性 | 頭部阻塞,丟包重傳導致抖動 | 可選擇性重傳、FEC、冗餘編碼,延遲更可控 |
| 連接狀態 | 有狀態,防火牆/NAT 易斷開 | 無狀態,適合廣播、多播、移動網絡切換 |
| 適用場景 | 文件傳輸、HTTP、數據庫同步 | 實時音視頻、遊戲狀態同步、物聯網指令下發 |