UDP實現簡單的超時重傳

 
 
 
 
 
衆所周知~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; } /*這個程序……反正大家看懂就好啦~省略了很多檢驗機制……別跟我學…………*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章