首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Linux UDP 應用層實現超時重傳:signal + sigaction 完整方案

     編輯:關於C++

問題背景

UDP 無連線、不可靠。雙方若都阻塞在 recvfrom,一方封包遺失將導致雙向永久阻塞。需在應用層實現超時重傳機制。

阻塞 I/O 模型與超時手段

  • Linux 預設套接字為阻塞模式。
  • 打破阻塞的三種方式:
    1. alarm() + 信號 SIGALRM
    2. 設定 SO_RCVTIMEO/SO_SNDTIMEO (需核心支援)
    3. select()/poll()/epoll() 多路複用

本文採用方案 1,配合 sigaction 精確控制信號行為。

關鍵點:sigaction 旗標 SA_NODEFER (舊稱 SA_NOMASK)

預設信號處理函數執行期間會阻塞同一信號,且返回後**自動重啟**被中斷的系統呼叫 (如 recvfrom),導致超時無效。需設定 SA_NODEFER (不阻塞自身信號) 並**不設定** SA_RESTART,使系統呼叫以 EINTR 失敗返回,由應用層處理重傳邏輯。

核心程式碼架構

#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>

void alarmed(int signo) {
    // 發送特殊封包通知對端重傳
    sendto(sockfd, "RETRANSMIT", 10, 0, (sockaddr*)&peer, sizeof(peer));
}

int main() {
    struct sigaction act = {};
    act.sa_handler = alarmed;
    act.sa_flags = SA_NODEFER; // 關鍵:不阻塞 SIGALRM,不自動重啟系統呼叫
    sigaction(SIGALRM, &act, NULL);

    for (;;) {
        alarm(60); // 60 秒超時
        ssize_t n = recvfrom(sockfd, buf, sizeof(buf), 0, (sockaddr*)&peer, &len);
        if (n < 0) {
            if (errno == EINTR) continue; // 超時觸發,alarm handler 已發送重傳請求
            perror("recvfrom");
        }
        // 處理正常收到的資料...
    }
}

完整流程說明

  1. 設置 SIGALRM 處理函數 alarmed,旗標 SA_NODEFER。
  2. 進入主迴圈,每次 recvfrom 前呼叫 alarm(60)。
  3. 若 60 秒內無資料,核心送 SIGALRM,recvfrom 返回 -1, errno=EINTR。
  4. alarmed 發送協定約定的「重傳請求」封包給對端。
  5. 主迴圈捕獲 EINTR 繼續 recvfrom 等待重傳資料。
  6. 對端收到重傳請求,重發上一個封包。

工程化補充

  • **封包序號/ACK**:實際專案需加入序號、確認機制、指數退避重傳、去重等。
  • **多執行緒替代**:現代高併發服務建議改用 epoll + 定時器堆 或專用網路庫 (libuv, ASIO, muduo)。
  • **信號安全**:alarmed 中僅呼叫 sendto (async-signal-safe),避免非安全函數 (如 malloc, printf)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved