三、UDP網絡編程 &&TCP、UDP的區別

一、UDP概述

UDP 是 User Datagram Protocol 的簡稱,稱爲用戶數據報協議,是一種無連接、不可靠的、基於數據報的傳輸層協議。

【1. UDP的特點】:

  • 無連接; 在發送數據之前不需要建立連接,即不需要listen監聽連接,accept接受連接。故規定UDP每次發送數據都需要攜帶完成的目的地址,否則無法發送。
  • 不可靠; 不會對數據包的順序進行檢查,不能保證數據包的先後順序,更沒有確認重傳機制,不會返回錯誤信息。
  • 面向數據報: 對於客戶端傳來的數據,不會進行拆分,合併。每一次收到的數據就類似用報紙包着,一塊一塊的。

【2. UDP的優缺點】:

優點:

  • 運行速度快;不需要進行三次握手連接,確認應答,擁塞控制等協議來確保數據可靠,故UDP傳遞數據的速度很快。
  • 較安全;沒有TCP那些機制,UDP被攻擊者利用的漏洞就會少一些,但較安全不是決定安全,UDP有時也會受到UDP洪泛攻擊。

缺點:
不可靠,丟包嚴重;UDP不提供可靠性傳輸,沒有TCP那些機制,它只是把應用程序傳給IP層的數據報發送出去,並不能保證它們能到達目的地。

【3. UDP使用場景】:

UDP 適用於對實時性要求比較高,但對可靠性要求不高的場景,如視頻傳輸、實時通信等,因爲它們即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。比如我們聊天用的 ICQ 和 QQ 就是使用的 UDP。

【4. 運行於 UDP 協議之上的協議】:

  • DHCP 協議:動態主機配置協議,動態配置 IP 地址。
  • NTP 協議:網絡時間協議,用於網絡時間同步。
  • BOOTP 協議:引導程序協議,DHCP 協議的前身,用於無盤工作站從中心服務器上獲取 IP 地址。
  • TFTP 協議:TCP/IP 協議族中的一個用來在客戶機與服務器之間進行簡單文件傳輸的協議,提供不復雜、開銷不大的文件傳輸服務。端口號爲 69。
  • SNMP 協議:簡單網絡管理協議。

二、UDP數據報

UDP數據報有兩個字段,數據字段和首部字段,首部字段很簡單,只有8字節,由四個字段組成,每個字段的長度都是兩個字節,如下圖所示:
在這裏插入圖片描述
各字段意義如下:

  • 源端口:表示發送端端口,字段長16位,該字段爲可選項,有時可能不會設置源端口號,沒有源端口號的時候該字段的值設置爲0,可用於不需要返回的通信中。

  • 目標端口號:表示接收端端口,在交付報文時必須要使用到。

  • 數據報長度:UDP用戶數據報長度,其最小值爲8,表示僅有首部。

  • 檢驗和:檢測UDP用戶數據報在傳輸中是否有錯,有錯就丟棄。在計算檢驗和時需要附加UDP僞首部在UDP數據報之前,原因是:
    TCP/IP中識別一個進行通信的應用需要5大要素,它們分別爲:源IP地址,目標IP地址,源端口,目標端口,協議號。然而UDP首部只其中的兩項(源端口,目標端口),爲了驗證一個通信中必要的5識別碼是否正確,就需要引入12字節的僞首部,包含源IP地址,目標IP地址,協議號等信息。

    當檢驗和字段中填入0,表示不進行檢驗和計算,雖然可以提高速度,但是一旦出現問題,會對其他通信造成不好的影響,故推薦使用檢驗和檢查。

根據UDP數據報的結構,可以看出UDP沒有采取任何機制來保證數據的可靠性。

三、UDP編程函數

UDP編程函數和TCP編程所用函數區別不大,主要區別是讀寫函數,其他函數只有參數上的改變。UDP編程因爲無連接,所以省略了很多函數,必須使用的函數有

(一)socket函數

創建socket,需要將type參數表示指定服務器類型,改爲SOCK_DGRAM數據報服務,用於UDP。

(二)bind函數

有兩種使用情況:

  • 客戶端先發消息: 服務器必須調用這個函數,將定義的socket地址和創建的socket綁定,這樣使得客戶端能知道它發數據的目的地址/端口,這樣綁定後,服務器要先接受客戶端的信息即客戶端必須先發送信息;客戶端的套接字可以不綁定自身的地址/端口,UDP在創建套接字後直接使用sendto()發送數據,隱含操作是,在發送數據之前操作系統會爲該套接字隨機分配一個合適的UDP端口,將該套接字和本地地址信息綁定
  • 服務端先發消息: 如果服務器程序先發送數據給客戶端,那麼服務器就需要知道客戶端的地址信息和端口,那麼就不能讓客戶端的地址信息和端口號由客戶端所在操作系統分配,而是要在客戶端程序指定了,這是客戶端也需要調用bind()函數。

(三)recvfrom讀取數據

UDP中用於讀取數據的系統調用和TCP不一樣,因爲無連接,所以每次讀取/寫入數據時都必須指出目標地址纔可以。讀取數據函數原型爲:

# include <sys/types.h>
# include<sys/socket.h>
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen)
          //成功返回讀取的數據長度,失敗返回-1

參數:

  • sockfd: 文件描述符,讀取sockfd上的數據。
  • buf: 緩衝區的位置
  • len: 緩衝區的大小
  • flags: 一般設置爲默認,0
  • src_addr: 發送數據端的socket地址,所以要定義socket結構體,表示發送數據的主機地址信息的結構體。即函數調用成功後,發送數據端的信息保存在此結構體中,我們可以獲取相關信息。
  • addrlen: 指定該地址的長度,需要傳入地址。

常見操作爲

struct sockaddr_in cli_addr;//保存發送端信息結構體
socklen_t len =sizoef(cli_addr);
int n=recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli_addr,&len);

(四)sendto寫入數據

系統調用原型爲:

# include<sys/types.h>
# include<sys/socket.h>
ssize_t  sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);
              //成功返回寫入的數據長度,失敗返回-1

參數:

  • 前四個參數和recvfrom一樣。
  • dest_addr:表示接收端的socket地址,故需要定義socket結構體,並將其初始化爲接收端的信息,這樣纔可以指定將信息發到哪個接收端上。
  • addrlen:長度,不需要傳入地址

一般使用方法:

struct sockaddr_in ser;//定義接收端
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;//初始化
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=sendto(sockfd,data,strlen(data)-1,0,(struct sockaddr*)&ser,sizeof(ser));//發送數據

(五)close關閉

用法一樣,傳入文件描述符關閉即可。

四、UDP編程

(一)編程流程

首先我們需要明白這幾個點:

  • UDP服務器被動的,所以必須進行地址綁定,即bind函數調用,客戶端可根據需求判斷是否進行bind函數的調用。
  • 如果只在服務器調用bind,那麼客戶端必須先調用sendto,TCP的send和recv順序無所謂,因爲已經建立了連接,服務器已經知道客戶端是誰了,所以無所謂。但是UDP是無連接的,故客戶端必須先sendto把自己的信息發送過去,讓服務知道自己是誰,否則服務器完全不知道客戶端是誰,就無法發信息。
  • 同樣我們讓客戶端和服務器進行循環的通信,所以收發數據在while循環內。

那我們寫出UDP編程流程的僞代碼:

在這裏插入圖片描述

我們畫出通信流程:
在這裏插入圖片描述

(二)編程實例

實現客戶端和服務器通信,並觀察多個客戶端和通信的情況。

Udpser.c:

# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
# include<unistd.h>
# include<sys/socket.h>
# include<sys/types.h>
# include<arpa/inet.h>
# include<netinet/in.h>


int main()
{
    int sockfd=socket(PF_INET,SOCK_DGRAM,0);
    assert(sockfd!=-1);

    struct sockaddr_in ser_addr;
    memset(&ser_addr,0,sizeof(ser_addr));
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_port=htons(6000);
    ser_addr.sin_addr.s_addr=inet_addr("127.0.0.1");

    int res=bind(sockfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));//需要綁定
    assert(res!=-1);
    while(1)
    {
        struct sockaddr_in cli_addr;//獲取客戶端的地址信息,存儲在這裏
        socklen_t len=sizeof(cli_addr);
        memset(&cli_addr,0,sizeof(cli_addr));

        char buff[128]={0};
        int n=recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli_addr,&len);
        if(n<=0)
        {
            printf("recvfrom error\n");
            continue;
        }
        printf("%s:%d--->%s\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),buff);
        int res=sendto(sockfd,"ok",2,0,(struct sockaddr*)&cli_addr,len);
        if(res<=0)
        {
            printf("sendto error\n");
            continue;
        }
    }
    close(sockfd);
    exit(0);
}

Udpcli.c:

# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
# include<unistd.h>
# include<sys/socket.h>
# include<sys/types.h>
# include<arpa/inet.h>
# include<netinet/in.h>


int main()
{
    int sockfd=socket(PF_INET,SOCK_DGRAM,0);
    assert(sockfd!=-1);

    struct sockaddr_in ser_addr;//定義服務端結構體,客戶端需要先給服務端發信息
    memset(&ser_addr,0,sizeof(ser_addr));
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_port=htons(6000);
    ser_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    while(1)
    {

        char buff[128]={0};
        printf("input:");
        fgets(buff,127,stdin);
        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
        int res=sendto(sockfd,buff,strlen(buff)-1,0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
        assert(res!=-1);
        memset(buff,0,128);
        int n=recvfrom(sockfd,buff,127,0,NULL,NULL);//因爲已經和服務器建立連接,所以不用指定了,使用默認即可
        printf("recvfrom ser data:%s\n",buff);
    }
    close(sockfd);
    exit(0);
}

運行:
我們運行一個客戶端連接服務器,可以看到服務器可以和它正常通信

在這裏插入圖片描述

再打開一個客戶端,發送數據,發現服務器也可以第二個客戶端進行通信,可以接收到客戶端發送的信息。
在這裏插入圖片描述

總結: UDP是無連接的,所以接受誰的都可以,誰先到我先處理誰的,接下來處理下一個,不會存在和TCP一樣的情況只接受一個客戶端的,打開第二個客戶端無法處理

五、TCP、UDP的區別

(一)面向連接 VS 無連接

【面向連接:】 就是在通信雙方進行通信之前,必須先建立連接,在通信過程中,整個連接的情況一直被實時的監控和管理,通信結束後,應該釋放這個連接。
【無連接:】 指兩個實體之間的通信不需要事先建立好連接,需要通信時,直接將信息發送到網絡中,讓該信息的傳遞在網絡上盡力而爲的往目的地傳送。

(一)字節流 VS 數據報

1. 字節流

我們說TCP是一種字節流服務,那麼什麼是字節流服務,字節流服務產生的問題?

【1.字節流的概念】:

基於流的數據就稱爲字節流,它源源不斷地從通信地另一端流入另一端,發送端可以逐個字節地向數據流中寫入數據,接收端也可以逐字節地將它們讀出。

【2.字節流特點】:

看了概念應該還是不太明白,我們將上一次寫的TCP代碼進行一個修改,實現服務器一次只能讀取5字節的數據,我們運行,觀察結果:

 int num=recv(clientfd,buff,5,0);//接收數據
  • 可以看到當我們客戶端第一次發送的數據【hello world】超過5字節,結果如下:

在這裏插入圖片描述

服務器會一次讀取5字節的數據,讀取多次,直至將緩衝區數據讀完,但只給客戶端回覆一次確認信息,其他確認信息都在客戶端的緩衝區裏

  • 當客戶端再一次發送超過5字節的數據【this is tcpcli】,客戶端會將第一次服務器給它的回覆信息一次性打出;第三次輸入不超過5字節數據【end】,客戶端一樣全部打出回覆信息

在這裏插入圖片描述
這就是字節流服務,我們將整個數據過程用下圖表示:
在這裏插入圖片描述
我們可以從上面的圖看到,應用層與TCP傳輸層之間的數據交互是大小不等的數據塊,但是TCP把這些數據塊視爲一串無結構的字節流全部存儲到緩衝區當中,故我們可以總結出字節流服務的特點

  • 存在緩衝區,所以不會出現數據不及時讀就丟失的情況。
  • 如果服務器不能一次性將發送的數據讀完,那麼就會存在發送端send的次數與接收端recv的次數不相等的情況。
  • 發送端send的次數與應用層封裝的TCP報文段的個數也不相等,因爲都在緩衝區放着,發送幾次取決於send每次發送的大小。
  • 一次recv如果沒有將數據報讀取完成,剩餘的數據會繼續保存在緩衝區中,下次recv會接着上一次的位置讀取數據,這就會導致粘包問題

【3.粘包問題:】:

粘包問題會發生在TCP協議中,因爲其數據傳輸是基於字節流的,TCP頭部也沒有標識數據長度的字段,因此TCP會產生粘包和拆包現象:

原因:

  • 寫入TCP緩衝區的數據小於緩衝區的大小,多次寫入緩衝區的數據將被一次發送出去,將會發生粘包問題
  • 接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包

解決:

  • 發送端爲每個數據報添加包首部,至少包含數據的長度和數據偏移。
  • 發送端設置發送數據報的固定長度,接收端在接收緩衝區讀取固定長度的數據,這樣就自然地把每個數據報拆分開來。
  • 可以在數據報邊界之間設置特殊符號,這樣接收端就可以根據符號進行分割。

2. 數據報

【1.數據報的概念】:

數據報服務,每個數據報都有一個長度,接收端必須以該長度爲最小單位將所有內容一次性讀出,否則數據將被截斷並丟棄。

【2.數據報的特點】:

我們也修改一下UDP服務端的代碼,讓服務端每次只接收5字節的數據,結果如下:

int n=recvfrom(sockfd,buff,5,0,(struct sockaddr*)&cli_addr,&len);

在這裏插入圖片描述
我們可以看到第一次發送的數據大小5字節,那麼只收到hello,第二次再發送,會收到新的數據,這就表示UDP不會將沒讀取完的數據保存,它會直接丟棄。

這就是數據報服務,整個數據傳輸流程如下:
在這裏插入圖片描述

應用層與UDP傳輸層之間的數據交互是大小不等的數據塊,UDP接收發送也是以大小不等的數據端發送。故我們總結出數據報服務的特點

  • 發送端每執行一次寫操作,UDP就將其封裝成一個UDP數據報發送,所以不會產生粘包問題。
  • 接收端必須及時針對每一個UDP數據報執行讀操作,否則就會丟包,即發送端sendto發送100字節,接收端必須一次性接收,否則剩下的數據被丟掉。
  • 如果接收端可以接收的數據長度小於發送端,那麼UDP數據報會被截斷
  • UDP沒有真正意義上的發送緩衝區,調用snedto會直接交給內核,由內核數據傳給網絡層協議進行後續的傳輸動作。
  • UDP具有接收緩衝區,但是這個接收緩衝區不能保證收到的UDP報的順序;一旦緩衝區滿了,那麼到達的數據就會被丟棄。

六、TCP && UDP的區別

我們對於TCP和UDP協議的基本特點和概念已經瞭解,下面從不同角度分析TCP,UDP協議的區別:

角度 TCP UDP
連接 面向連接 無連接
可靠性 可靠服務,通過校驗和,序號確認號,超時重傳,滑動窗口等機制保證報文無差錯,不丟失,不重複,按序到達 盡最大努力交付,不保證可靠性
效率 TCP因爲機制複雜,所以實時性,工作效率低 UDP機制簡單,所以實時性、工作效率高,使用於實時性要求比較高的通信或廣播通信等場景
傳輸場景 只能用於一對一通信 可用於一對一,一對多,多對一,多對多等傳輸場景
資源 對系統資源要求多,因爲要管理連接,可靠傳輸 佔用資源少
內存開銷 TCP有20字節的首部開銷,大 UDP只有8字節的首部開銷。

加油哦!🍝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章