首頁 > 軟體

Linux之UDP協定及其程式設計全流程

2023-03-23 22:06:54

UDP協定的特點

UDP 不提供可靠性的傳輸,它只是把應用程式傳給 IP 層的資料包傳送出去,但是並不能保證它們能到達目的地。

由於 UDP 在傳輸資料包前不用在客戶和伺服器之間建立一個連線,且沒有超時重發等機制,故而傳輸速度很快。

  • 無連線
  • 不可靠
  • 資料包服務

UDP發出的封包不經過確認,可以繼續傳送。傳送成功與否都不管,盡最大能力去傳送,丟包也不負責。有自己的使用特點:適合於做視訊(實時性)適合於即使丟包了,處理起來也比較方便。

適合於攝像頭以恆定速率發,對方以恆定速率收,丟包了繼續發,可以實時。

但是如果是TCP,如果丟包,會重發,時間花銷大了,不能實時。不適合做攝像頭和視訊。

UDP的程式設計流程

UDP介面原型

接收

int recvfrom(int sockfd,void *buf,size_t size,int flag,struct sockaddr *peer_addr,socklen_t *addr_len);
  • peer_addr:用來儲存recvfrom接收到的資料是來自哪臺主機的地址資訊
  • addr_len:地址結構的長度

傳送

int sendto(int sockfd,void *buf,size_t size,int flag,struct sockaddr *peer_addr,socklen_t addr_len);
  • peer_addr:用來指定資料的接收方的地址資訊
  • addr_len:地址資訊的長度

範例程式碼

UDP伺服器端

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>

#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int main()
{
    //SOCK_DGRAM表示使用的是UDP協定
    int sockfd = socket(AF_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);
    //將點分十進位制的地址字串轉為unit32型別的值
    ser_addr.sin_addr.s_addr = inet_addr("192.168.246.128");

    int res = bind(sockfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
    assert(res != -1);

    //迴圈接受不同使用者端的資料
    while(1)
    {
        char buff[128] = {0};

        struct sockaddr_in cli_addr;
        socklen_t cli_len = sizeof(cli_addr);
        int n = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli_addr,&cli_len);
        if(n <= 0)
        {
            break;            
        }

        printf("%s:%d -- %sn",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),buff);   
     
        n = sendto(sockfd,"OK",2,0,(struct sockaddr*)&cli_addr,cli_len);
        if(n <= 0)
        {
            break;
        }
    }

    close(sockfd);
    exit(0);
}

UDP使用者端

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.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(AF_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("192.168.246.128");

    while(1)
    {
        printf("請輸入:");
        char buff[128] = {0};
        fgets(buff,127,stdin);

        if(strncmp(buff,"end",3) == 0)
        {
            break;
        }

        int n = sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
        if(n <= 0)
        {
            break;
        }

        memset(buff,0,128);

        int m = recvfrom(sockfd,buff,127,0,NULL,NULL);//伺服器地址資訊已知,無需儲存直接傳入NULL
        if(m <= 0)
        {
            break;
        }
        printf("%sn",buff);
    }   

    close(sockfd); 

    exit(0);
}

兩個使用者端同時向伺服器端傳送資訊

多個使用者端可以和伺服器一起連結通訊。recvfrom並不是只等第一個或者第二個使用者端,而是誰給它發,它就收誰的。

如果在使用者端保持執行狀態的情況下,將伺服器端關閉,然後再把伺服器端重新執行起來,這時候使用者端傳送資料,伺服器端是可以收到的。

因為UDP本來就沒有建立連線。如果伺服器端關了,使用者端send就失敗了。 封包丟了就丟了,不會理會。不管關閉哪一端,對方端都不知道這件事情,彼此無關係,無影響。

如果讓伺服器端一次只接受一個字元,我給你發一個封包,你去收這個封包,你recvfrom,你把這個封包拆開,你讀取1個字元,後面的不讀,直接就丟掉了。

UDP的報頭結構

 

UDP的報頭固定是8個位元組!

  • UDP的報文段長度 – 表示這個UDP報文段的報頭+資料部分的總長度 一個UDP報文段資料部分的長度為總長度 - 8
  • 冗餘檢驗碼 – 會對整個UDP資料包進行冗餘校驗

UDP的優勢

  • 沒有確認機制和超時重傳機制,傳送方傳送報文段的效率就很高。
  • 頭部固定部分比較小,一個UDP報文段所攜帶的上次協定的資料就比TCP多一點。
  • UDP的實現相對比較簡單。

UDP的資料包服務

  • sendto和recvfrom的次數是一一對應的。
  • sendto一次,底層就傳送一個UDP報文段,對方就接受這一個UDP報文段。
  • 如果一次recvfrom沒有將一個UDP報文段中的資料讀取完成,則剩餘的資料會被丟棄。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com