首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

UDP 無序特性與應用層序號設計:保證數據包順序處理的正確姿勢

     編輯:程式設計綜合問答

核心事實

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)

接收端處理邏輯:滑動窗口重排

  1. 緩存區:環形緩存或 std::map<uint32_t, Packet> 存儲亂序到達的包。
  2. 期望序號:維護 next_expected_seq,初始 0。
  3. 收包流程:
    1. 解析頭部 seq。
    2. 若 seq == next_expected_seq → 直接交付上層,next_expected_seq++,檢查緩存區是否有後續連續包,循環交付。
    3. 若 seq > next_expected_seq → 存入緩存區(去重)。
    4. 若 seq < next_expected_seq → 重複包,丟棄。
  4. 超時重發/丟包處理:設置定時器,超過 MAX_WAIT_MS 仍未收到期望包 → 判定丟包,觸發 NACK 請求重發或上層錯誤處理。

Java 簡易實現參考

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?

特性TCPUDP + 應用層序列化
順序保證 內核自動 應用層實現
延遲特性 頭部阻塞,丟包重傳導致抖動 可選擇性重傳、FEC、冗餘編碼,延遲更可控
連接狀態 有狀態,防火牆/NAT 易斷開 無狀態,適合廣播、多播、移動網絡切換
適用場景 文件傳輸、HTTP、數據庫同步 實時音視頻、遊戲狀態同步、物聯網指令下發

最佳實踐清單

  • 序號用 32 位無符號整數,空間足夠大(42 億),無需擔心回繞(配合窗口判斷)。
  • 發送端固定間隔發送心跳/序號同步包,接收端可檢測連接存活。
  • 大數據分片時,同一業務包共用 seq,frag_idx/frag_cnt 標識分片,組裝完整後再按 seq 重排。
  • 生產環境建議使用成熟庫:ENet (C)、KCP (C/Go)、Netty + 自定義協議 (Java)、Quic-go (Go)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved