眾所周知~UDP是一個無連接協議,因此靠它來傳輸的話是不可靠的,即使是數據包丟失 也不會報錯。但是,在編寫Linux上的socket程序時,卻可以用簡單的方法,在應用層實現超時 重傳,讓UDP可靠一些。(這次說的方法最好用於兩個程序間通信——也許只能用 於兩台機器通信)首先~我介紹一下Linux下,I/O操作的阻塞模式:
在Linux下,I/O 操作有四種模式,分別為:阻塞式I/O,非阻塞式I/O,多路復用I/O,一擊信號驅動I/O,這 次需要用到的是阻塞式I/O。阻塞式I/O是最簡單,最常用但也是效率最低的一個。在默認模 式下,所有的套接字都是阻塞模式,即:當用戶調用這些函數時,函數將一直阻塞下去,直 至有某個事件發生。具體事件依函數而定,比如:調用讀函數,由於緩存中還沒有數據,而 使得讀函數發生讀阻塞;同理,也可能在調用寫函數的時候發生寫阻塞;除此之外,還有調 accept函數的時候,由於沒有客戶連接服務器,使得其發生阻塞;調用connect函數時,由於 三次握手沒有結束,使得其發生阻塞等等。也就是說~在沒有特定事件發生的情況下,函數 將什麼也不干而等待事件發生,事件發生後則繼續執行程序。而有些時候,由於某些原因, 會使得函數永遠處於阻塞模式(比如:客戶用UDP給服務器傳送數據的數據丟失,使得服務器 端的recvfrom函數始終處於阻塞模式)這就需要調用某些函數使這些函數不再阻塞,具體方 法有:
1、使用信號:比如調用alarm函數
2、在套接字上設置SO_RCVTIMEO和 SO_SNDTIMEO選項,使得其阻塞有時間限制
3、時間選擇通過select函數來實現
好啦~阻塞式I/O就說到這裡,言歸正傳~繼續討論相對可靠一些的UDP~
前 面已經說了,假如使用阻塞模式,那麼,當一個數據包還沒有到達目的地時,那麼數據包的 目的端程序就會處於阻塞狀態,因此不能調用sendto函數給發送端,而發送端此時也在 recvfrom下阻塞了,等待對方傳來消息。
由於前面已經說了,這是只有兩個程序間通 信,因此,雙方程序的生死之大事、前途、命運……都掌握在傳輸的那個數據 包上了,如果那個數據包不爭氣(沒准是路由問題,線路問題等等),中途數據包丟失了, 那麼,雙方都將永遠處於阻塞狀態:發送端阻塞在recvfrom上,等待接收端回話;接收端也 阻塞在recvfrom上,等待發送端傳來的消息。可偏偏那消息不爭氣,傳不過來 ……難道這倆程序就這麼掛了?
如果只有sendto,recvfrom函數而沒有 超時機制,那……就為這倆程序祈禱吧……大約他們倆就得掛這 等關機或者被處以極刑(就是kill啊~)…………不過~聽了我 下面說的~就可以解決這問題~同時,讓UDP層上面的應用層有超時重傳的能力~(暈~我這 不是作廣告啊………………)
其實看到這裡 ,大家已經多半想到了如何處理這問題:只要有一方退出阻塞模式,發個數據包,那兩個程 序就都解放了~怎麼讓一個程序退出阻塞模式呢~其實很簡單啦~沒錯~可以用alarm函數~ 一旦到時間~給程序傳一個SIGALRM的中斷消息就可以啦~讓程序先處理這消息,然後攔一下 SIGALRM消息,愛怎麼處理怎麼處理~解決~
/****************函數:alarm函數( 知道的可以不用看)****************/
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:從調 用該函數算起,seconds秒後返回向調用進程傳送一個SIGALRM消息
參數:seconds以 秒為單位的整數
/****************************************************************/
看到這裡也許你以為一切都解決了,但是還有一個容易被人忽視的問題:在Linux中,默認處 理中斷的方式是:
當從中斷調用返回時,繼續執行被中斷的系統調用(用在剛才說的 例子上就是:繼續redvfrom……)這中默認處理方式大多數時候很有用,但是 我們這裡就不行了,那這個問題怎麼解決呢?要解決這個問題,就不能單純的用signal函數 去設置中斷處理程序了,而是要用另一個函數:sigactiong,
sigaction函數如下:
/***********函數:sigaction函數(知道的可以不用看 ****************/
#include <signal.h>
#include <types.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
功能:攔截下signum消息,用act所給的方式處理,將原來的處 理方式存在oldact(一般oldact設為NULL);
參數:signum:需要攔截的消息,這裡 是SIGALRM;
act:處理中斷的方式,是一個結構體,後面會介紹這結構體;
oldact:用來存儲原來的處理方式,一般為NULL,表示忽略;
/****************************************************************/
/ ***********結構體:struct sigaction(知道的可以不用看) *******/
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
成員:第一個sa_handler就是中斷處理程序的入口,比如:要用alarm程序 處理這個中斷,就講此值設為alarm;
sa_mask:表示在中斷處理中要屏蔽的中斷;
sa_flags:這是很關鍵的東西~它包含了一些影響中斷處理過程方式的標志,具體取 值如下:
SA_NOCLDSTOP:這表示如果所處理的中斷是SIGCHLD,由於收到其他信號而 導致了子進程終止,將不發送SIG_CHLD;
SA_ONESHOT or SA_RESETHAND:sa_handler 所指向的中斷處理程序只被執行一次,之後將設為默認的中斷處理程序;
SA_RESTART :讓被處理的系統調用在中斷返回後重新執行;
SA_NOMASK or SA_NODEFFER(這就是 我們要用的):在中斷處理程序執行時,不平比自己的中斷信號;
要考慮的成員就是 上面的兩個至於其他兩個於要處理的問題關系不大,大家可以查書看看
/***********************************************************/
好啦~ 到這說的就差不多了~下面舉個例子(這不是個完全的程序,只是寫了我們所關心的部分)
代碼:
/*省略include*/
int main()
{
/*省略部分變量定義*/
struct sigaction alr;
memset(&alr,0,sizeof(struct sigaction));
alr.sa_handler=alarmed;/*用alarmed函數處理*/
alr.sa_flags=SA_NOMASK;/*具體含義見前文*/
alr.sa_restorer-NULL;
sigaction(SIGALRM,&alr,NULL);/*需要捕捉SIGALRM消息,具體處理方式在alr結構體中 ,不關心原來的處理方式*/
/*假設有一個已經設置好的struct sockaddr_in的結構體,名字是addr,和一個已經用bind 函數處理完的套接字,變量名是:fd*/
for( ; ; )
{
alarm(60);/*60秒後,如果仍處於阻塞狀態就重傳*/
recvfrom(/*具體參數我就不說了,反正就是調用了一個recvfrom函數);*/
/*以下省略若干*/
}
}
void alarmed(int signo)
{
sendto(/*向發送端傳送特殊數據包(自己定義,愛什麼樣什麼樣),當發送端收到這數據包 後,就重傳一遍剛才發送的數據(自然這個功能你得自己寫……)*/
return;
}
/*
這個程序……反正大家看懂就好啦~省略了很多檢驗機制 ……別跟我學…………*/