我們知道,使用 read 或 recvfrom 函數從 socket 上讀取資料時,可能會導緻阻塞。特别是 UDP 伺服器,稍有不甚,資料報就丢失就會直接導緻程式假死(阻塞在 recvfrom)。
還有 connect 函數,如果去連接配接一個網絡上不存在的主機,需要等待很長時間,可能長達 2 分鐘。如果我們能控制它在指定的時間内連接配接不上,就直接報錯,行不行呢?
很可惜,上面的問題 linux 并未直接提供帶逾時的函數幫我們解決,是以我們需要一些奇技淫巧來完成上面的功能。
1. 如何設定逾時?
常用的有 3 種方法來設定逾時:
- 1) 使用 alarm 函數,一旦到期就産生 SIGALRM 信号,打斷正在執行的系統調用。
- 2) 使用 select 函數。
- 3) 使用套接字選項 SO_RCVTIMEO 和 SO_SNDTIMEO.
關于這幾個方案,有幾點要說明:
- 三個技術都可用于輸入和輸出(read 和 write)
- 對于阻塞的 connect 隻能使用方案一,對于非阻塞的 connect,隻能使用 select.
- 方案三對 connect 不适用。
- 方案一和二适用于任何描述符,而方案三僅适用于套接字描述符。
2. 使用 alarm 設定逾時
它的僞代碼如下,這裡以 connect 為例:
void sighandler(int sig) {
// 信号處理函數,什麼也不用做
return;
}
int connect_timeo(/*...*/, int nsec) {
// 注冊信号
registSignal(SIGALRM, sighandler, &oldhandler);
// 開啟一個 nsec 秒的 alarm
alarm(nsec);
ret = connect(/*...*/);
// 如果被信号打斷,則表示逾時
if (ret < 0 && errno == EINTR) {
errno = ETIMEDOUT;
}
// 删除 alarm
alarm(0);
registSignal(SIGALRM, oldhandler);
return
3. 實驗代碼
代碼托管在 gitos 上,請使用下面的指令擷取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已經 clone 過這個代碼了,請使用
git pull
更新一下。本節程式所使用的程式路徑是
unp/program/advcio/conn_timeo/conn_timeo.cc
,另一個使用 alarm 為 recvfrom 設定逾時的例子是
unp/program/advcio/read_timeo/read_timeo_alarm
.
4. 實驗結果
ip 位址 192.168.166.4 在網絡上并不存在,直接連接配接它就會導緻 connect 函數阻塞,一直在發送 SYN 請求。
- 不使用逾時的 connect

圖1 不使用逾時的 connect
可以看到,預設的 connect 函數逾時值約為 127 秒。
- 使用逾時的 connect
通過指令行
--timeo
選項傳遞逾時值給 connec_timeo 函數。
圖2 使用逾時的 connect
5. 總結
- 掌握使用 alarm 編寫逾時函數的方法