網絡學習八-UDP

UDP C/S的典型函數(shù)調用

Paste_Image.png

UDP沒有像TCP那樣的連接,客戶端直接sendto向某服務器發(fā)送數(shù)據(jù),服務器端一直recvfrom阻塞,以接收任何客戶端發(fā)送的數(shù)據(jù)。

sendto和recvfrom函數(shù)

int sendto(int sockfd, const void* buff, size_t nbytes, int flag, const struct sockaddr* to, socklen_taddrlen);
int recvfrom(int sockfd, void* buff, size_t nbytes, int flag, struct sockaddr* from, socklen_t* addrlen); 
  1. 成功返回字節(jié)數(shù),失敗返回-1
  1. 這兩個函數(shù)相比較于read和write多了三個參數(shù)
    (1) flag后面說,這里先置為0
    (2) sendto的地址結構指明發(fā)送目的地的套接字地址。addrlen指明地址長度,為整數(shù)型。相當于TCP的connect中的套接字地址。
    (3) recvfrom的地址結構指明發(fā)送此數(shù)據(jù)報的發(fā)送端的套接字地址。addrlen為此套接字地址,為整型地址。相當于TCP的accept中的套接字地址。
  2. 寫一個長度為0的數(shù)據(jù)報是可行的,會形成一個只包含IP首部(20字節(jié))和UDP首部(8字節(jié))的IP數(shù)據(jù)報。所以recvfrom返回0,是可接受的。而不是像TCP那樣read返回0表示關閉連接。
  3. recvfrom的套接字地址參數(shù)可以是NULL,表示不關心數(shù)據(jù)是誰發(fā)的。此時addrlen也必須是NULL。

使用UDP書寫回射服務器

回射服務器代碼 點擊查看
回射服務器dg_echo代碼 點擊查看

  1. 首先正常情況下,函數(shù)永不會終止。它不像TCP連接那樣,還有終止連接的四次。
  1. 處理函數(shù)str_echo,是一個迭代函數(shù),不像TCP那樣是并發(fā)的。一般TCP服務器都是并發(fā)的,而UDP服務器都是迭代的。為何?下面說
  2. 每個UDP套接字都會有一個接收緩沖區(qū),類似于一個隊列。多個數(shù)據(jù)報到達UDP服務器,則會排隊,調用recvfrom函數(shù),從這個隊列頭取出數(shù)據(jù)報給進程。而TCP是為每個客戶一個連接fork一個子進程,并且每個連接一個套接字,每個套接字一個接收緩沖區(qū),所以我們要并發(fā)監(jiān)聽每個接收緩沖區(qū)。而UDP是任何客戶發(fā)送的數(shù)據(jù)報放入一個接收緩沖區(qū),所以根本無需什么并發(fā)服務器,也不可能做成并發(fā)的。
  3. str_echo函數(shù)是協(xié)議無關的。

使用UDP重寫回射客戶端

回射客戶端 點擊查看
回射客戶端dg_cli 點擊查看

  1. 沒有為客戶端指定本地端口,則客戶端第一次sendto的時候,內核自動分配。
  1. 這里recvfrom的套接字地址是空指針,這樣做是非常危險的
    因為可能任何主機給此客戶端的這個臨時端口發(fā)送一個消息時,此客戶端會認為這個消息是從服務器端發(fā)送過來的。造成消息混亂。所以這個是有問題的,下面解決。
  2. 數(shù)據(jù)報的丟失問題
    如果某次客戶端發(fā)送給服務器端的數(shù)據(jù)報丟失了,或者服務器端發(fā)給客戶端的數(shù)據(jù)包丟失了,則這都會引起客戶端永遠阻塞在recvfrom函數(shù)上。
    我們可以為recvfrom設置一個超時,但是超時還是不能完全解決這個問題。因為如果超時,我們不知道是客戶端->服務器端數(shù)據(jù)報丟失了,還是服務器->客戶端數(shù)據(jù)庫丟失了。
  3. 針對上面2提到的問題,我們試著獲取recvfrom的套接字地址和sendto發(fā)送的套接字地址是否一致,來決定此消息是否是來自對端服務器。

我們修改的str_cli函數(shù)如下:

void str_cli(int sockfd, FILE* fd, const struct sockaddr* servaddr, socklen_t addrlen)
{
    int nbytes;
    charbuff[MAXLINE],recvbuff[MAXLINE];
    struct sockaddr * fromaddr=new sockaddr();
    socklen_tfromaddrlen=addrlen;
    while(fgets(buff,MAXLINE,fd)!=NULL)
    {
        sendto(sockfd, buff,strlen(buff), 0, servaddr, addrlen);
        nbytes=recvfrom(sockfd,recvbuff, MAXLINE,0, fromaddr, &fromaddrlen );
        if(fromaddrlen !=addrlen || memcmp(servaddr, fromaddr, addrlen)!=0)
        {
            fputs("not from server, ignored",stdout);
            continue;
        }
        if(nbytes<0)
            err_sys("recvfrom error");
       recvbuff[nbytes]=0;
       fputs(recvbuff,stdout);
    }
}

可以看出,我們就是比較sendto時,服務器的套接字地址,和recvfrom得到的服務器套接字地址是否相等。這里我們先比較兩者的長度,然后再逐字節(jié)比較。
這里還是有問題的:
如果服務器是多宿主機,即兩個IP地址,如Ip1,Ip2。由于我們在寫服務器端程序時,bind函數(shù)的參數(shù)是通配IP,所以當我們sendto時,是使用Ip1,而服務器回射時,內核自動選擇了Ip2,則這會讓我們客戶端誤判該回射消息不是來自服務器端。
兩個解決辦法:
1> recvfrom得到IP后,查詢DNS獲得主機的域名,以判斷消息是否來自該主機。
2>服務器端為每個IP創(chuàng)建一個套接字,使用bind到每個IP地址。然后使用select監(jiān)聽這些套接字,等待其中一個變?yōu)榭勺x,說明客戶端使用的是這個IP,則服務器使用這個IP套接字回射就可以了。

服務器未運行

當我們先啟動客戶端,不啟動服務器端時,發(fā)生了什么:
我們從控制臺輸入一行數(shù)據(jù)回車,然后客戶端將永遠阻塞在recvfrom函數(shù)上。
底層機制:
數(shù)據(jù)發(fā)送到服務器主機上,發(fā)送主機的目的端口并沒有開啟,所以返回一個端口不可達的ICMP消息,這個消息是不會返回客戶端進程的(為何?)。所以客戶端用于阻塞在recvfrom上。
異步錯誤:本例中錯誤由sendto函數(shù)引起,但是sendto成功返回,ICMP到后來才返回錯誤。這就是異步錯誤。
一個基本規(guī)則:對于一個UDP套接字,由它引發(fā)的異步錯誤不返回給它,除非它已經連接。(注意UDP也有connect函數(shù))。
為何ICMP消息不會返回客戶端進程?Unix這樣設計的道理是什么?
假設我們使用客戶端連續(xù)發(fā)送3個消息,2個消息的目的服務器正常,最后一個服務器未啟動,則會有一個ICMP消息,假設這個消息被recvfrom獲取,recvfrom返回一個負值表示錯誤,然后errno為錯誤類型。但是注意此時客戶端并不知道是哪個目的服務器,哪個目的套接字出錯,內核無法告知進程,因為recvfrom此時返回的信息只是errno,所以Unix規(guī)定,不返回給進程。

給UDP套接字使用connect

上面提到未連接UDP套接字發(fā)生的異步錯誤,不會返回給進程,這里我們可以使用connect對一個UDP套接字進行連接。
Connect函數(shù)的調用和TCP一樣,參數(shù)指定目的服務器的套接字地址。注意沒有三次握手,只是檢查對端是否存在立即可知的錯誤(如目的主機不可達)。
已連接UDP套接字和未連接UDP套接字的不同:
(1) 已連接套接字,直接使用send、write,發(fā)送數(shù)據(jù)報給connect的目的服務器套接字。而不使用sendto。
(2)已連接套接字,直接使用read、recv或者recvmsg,接收來自connect目的服務器套接字的數(shù)據(jù)報,來自其他目的服務器套接字的數(shù)據(jù)報不會遞交給該套接字、也就意味著,已連接套接字只能和一個對端進行通信。
而未連接套接字顯然可以和任何多個對端通信。
(3) 已連接套接字發(fā)生異步消息會返回給進程,因為此時已經知道目的套接字。而未連接套接字不會返回給進程。
注意:一般對客戶端的UDP套接字進行connect,而服務器端還是sendto和recvfrom,connect只會影響本地套接字。
(1) 指定新的IP地址和端口號
(2) 斷開套接字。此時把套接字地址結構的地址族(IPv4的sin_family)設為AF_UNSPEC就可以了。

性能

當我們對一個未連接的UDP套接字連續(xù)sendto兩次,看看具體步驟:
連接套接字
發(fā)送第一個數(shù)據(jù)報
斷開套接字
連接套接字
發(fā)送第二個數(shù)據(jù)報
斷開套接字
如果兩個數(shù)據(jù)報是同一個目的套接字,則我們應該使用顯然connect,之后會提高效率。因為這樣只需要一個連接和斷開。
Unix中一個連接需要耗費一次UDP傳輸?shù)娜种坏拈_銷。
調用connect后調用兩次write涉及內核執(zhí)行如下步驟:
1、連接套接字
2、輸出第一個數(shù)據(jù)報
3、輸出低二個數(shù)據(jù)報

我們修訂上面的客戶端str_cli函數(shù)

void str_cli( int sockfd, FILE* fd,const struct sockaddr* servaddr, socklen_t addrlen)
{
    int nbytes;
    char buff[MAXLINE],recvbuff[MAXLINE];
       conect(sockfd,servaddr,addrlen);
    while(fgets(buff,MAXLINE,fd)!=NULL)
    {
        write(sockfd, buff, strlen(buff));
        nbytes=read(sockfd,recvbuff, MAXLINE );
        if(nbytes<0)
            err_sys("read error");
       recvbuff[nbytes]=0;
       fputs(recvbuff,stdout);
    }
}

如果為啟動服務器,此時我們再運行客戶端,輸入一個未啟動的服務器程序的主機Ip地址,然后從控制臺輸入一行數(shù)據(jù),輸出的結果就是readerror。這樣異步錯誤返回給進程了。
注意:此時我們connect時,并沒有發(fā)生錯誤,直到我們發(fā)送一個消息時才返回錯誤。而如果此時TCP的話,在connect時就會發(fā)生錯誤。原因?
因為UDP的connect不會觸發(fā)三次握手,而TCP的connect會觸發(fā)三次握手,發(fā)現(xiàn)目的端口不可達,則服務器會返回RST分組。

UDP缺乏流量控制

假設一個客戶端連續(xù)發(fā)送大量的數(shù)據(jù),則服務器端使用套接字接收緩沖區(qū)排隊接收這些數(shù)據(jù),但當發(fā)送來的數(shù)據(jù)超出套接字接收緩沖區(qū)時,服務器端就會自動丟棄到來的數(shù)據(jù)報,而此時客戶端和服務器端不會有任何的錯誤。
所以說UDP是沒有流量控制的。

UDP中的IP地址和端口號

  1. 未連接的UDP套接字,如果我們沒有bind,
    則當sendto時,內核選擇一個本地IP地址和端口號,所以同一主機上兩次連續(xù)的sendto,兩個消息的源IP地址和端口號可能都不一樣。
    而且,服務器端接收recvfrom后,回射消息,sendto時,可能造成回射消息的源IP地址和端口號和recvfrom消息的目的IP地址和端口號不一樣。
  1. 已連接UDP套接字,如果沒有bind
    同一個客戶端,多次connect同一個目的IP端口號時,已連接套接字的本地IP和端口號可能都是不一樣的。
    我們可以使用getsockname來獲取已連接UDP套接字的本地IP和端口號。
    我們使用recvmsg來獲取未連接的UDP套接字的本地IP和端口號。

我們使用select的TCP和UDP來重寫回射服務器和客戶端程序

點擊查看代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,699評論 19 139
  • 1)OSI與TCP/IP各層的結構與功能,都有哪些協(xié)議。 OSI分層 (7層):物理層、數(shù)據(jù)鏈路層、網絡層、傳輸層...
    ldlywt閱讀 2,396評論 0 26
  • iOS網絡HTTP、TCP、UDP、Socket 知識總結OSI 七層模型我們一般使用的網絡數(shù)據(jù)傳輸由下而上共有七...
    蝸牛也有夢想閱讀 2,650評論 0 3
  • 轉。。。。。。。。 SOCKET,TCP/UDP,HTTP,FTP (一)TCP/UDP,SOCKET,HTTP,...
    zeqinjie閱讀 3,390評論 1 53
  • 女神,現(xiàn)含義網絡泛指自己心儀的女性,同時也引申指容貌漂亮、有智慧以及綜合素質高的女性。女神原意是女性的神明或至尊,...
    助教Shirley閱讀 702評論 0 0

友情鏈接更多精彩內容