當前位置:首頁 » 操作系統 » linuxudp接收數據

linuxudp接收數據

發布時間: 2023-09-02 13:27:50

linux下怎麼設置udp接收緩存最大值

vi /etc/sysctl.conf
增加或修改 net.ipv4.udp_mem項
net.ipv4.udp_mem = min pressure max
再設一下 net.ipv4.udp_rmem_min
具體含義man udp 查看
完成後執行 sysctl -p 生效

Ⅱ 在Linux 上,編寫一個每秒接收 100萬UDP數據包的程序究竟有多難

首先,我們假設:
測量每秒的數據包(pps)比測量每秒位元組數(Bps)更有意思。您可以通過更好的管道輸送以及發送更長數據包來獲取更高的Bps。而相比之下,提高pps要困難得多。
因為我們對pps感興趣,我們的實驗將使用較短的 UDP 消息。准確來說是 32 位元組的 UDP 負載,這相當於乙太網層的 74 位元組。
在實驗中,我們將使用兩個物理伺服器:「接收器」和「發送器」。
它們都有兩個六核2 GHz的 Xeon處理器。每個伺服器都啟用了 24 個處理器的超線程(HT),有 Solarflare 的 10G 多隊列網卡,有 11 個接收隊列配置。稍後將詳細介紹。
測試程序的源代碼分別是:udpsender、udpreceiver。
預備知識
我們使用4321作為UDP數據包的埠,在開始之前,我們必須確保傳輸不會被iptables干擾:

Shell

receiver$ iptables -I INPUT 1 -p udp --dport 4321 -j ACCEPT

receiver$ iptables -t raw -I PREROUTING 1 -p udp --dport 4321 -j NOTRACK

為了後面測試方便,我們顯式地定義IP地址:

Shell

receiver$ for i in `seq 1 20`; do

ip addr add 192.168.254.$i/24 dev eth2;

done

sender$ ip addr add 192.168.254.30/24 dev eth3

1. 簡單的方法
開始我們做一些最簡單的試驗。通過簡單地發送和接收,有多少包將會被傳送?
模擬發送者的偽代碼:

Python

fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

fd.bind(("0.0.0.0", 65400)) # select source port to rece nondeterminism

fd.connect(("192.168.254.1", 4321))

while True:

fd.sendmmsg(["x00" * 32] * 1024)

因為我們使用了常見的系統調用的send,所以效率不會很高。上下文切換到內核代價很高所以最好避免它。幸運地是,最近Linux加入了一個方便的系統調用叫sendmmsg。它允許我們在一次調用時,發送很多的數據包。那我們就一次發1024個數據包。
模擬接受者的偽代碼:

Python

fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
fd.bind(("0.0.0.0", 4321))
while True:
packets = [None] * 1024
fd.recvmmsg(packets, MSG_WAITFORONE)

同樣地,recvmmsg 也是相對於常見的 recv 更有效的一版系統調用。
讓我們試試吧:

Shell

sender$ ./udpsender 192.168.254.1:4321
receiver$ ./udpreceiver1 0.0.0.0:4321
0.352M pps 10.730MiB / 90.010Mb
0.284M pps 8.655MiB / 72.603Mb
0.262M pps 7.991MiB / 67.033Mb
0.199M pps 6.081MiB / 51.013Mb
0.195M pps 5.956MiB / 49.966Mb
0.199M pps 6.060MiB / 50.836Mb
0.200M pps 6.097MiB / 51.147Mb
0.197M pps 6.021MiB / 50.509Mb

測試發現,運用最簡單的方式可以實現 197k – 350k pps。看起來還不錯嘛,但不幸的是,很不穩定啊,這是因為內核在核之間交換我們的程序,那我們把進程附在 CPU 上將會有所幫助

Shell

sender$ taskset -c 1 ./udpsender 192.168.254.1:4321
receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321
0.362M pps 11.058MiB / 92.760Mb
0.374M pps 11.411MiB / 95.723Mb
0.369M pps 11.252MiB / 94.389Mb
0.370M pps 11.289MiB / 94.696Mb
0.365M pps 11.152MiB / 93.552Mb
0.360M pps 10.971MiB / 92.033Mb

現在內核調度器將進程運行在特定的CPU上,這提高了處理器緩存,使數據更加一致,這就是我們想要的啊!
2. 發送更多的數據包
雖然 370k pps 對於簡單的程序來說已經很不錯了,但是離我們 1Mpps 的目標還有些距離。為了接收更多,首先我們必須發送更多的包。那我們用獨立的兩個線程發送,如何呢:

Shell

sender$ taskset -c 1,2 ./udpsender
192.168.254.1:4321 192.168.254.1:4321
receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321
0.349M pps 10.651MiB / 89.343Mb
0.354M pps 10.815MiB / 90.724Mb
0.354M pps 10.806MiB / 90.646Mb
0.354M pps 10.811MiB / 90.690Mb

接收一端的數據沒有增加,ethtool –S 命令將顯示數據包實際上都去哪兒了:

Shell

receiver$ watch 'sudo ethtool -S eth2 |grep rx'
rx_nodesc_drop_cnt: 451.3k/s
rx-0.rx_packets: 8.0/s
rx-1.rx_packets: 0.0/s
rx-2.rx_packets: 0.0/s
rx-3.rx_packets: 0.5/s
rx-4.rx_packets: 355.2k/s
rx-5.rx_packets: 0.0/s
rx-6.rx_packets: 0.0/s
rx-7.rx_packets: 0.5/s
rx-8.rx_packets: 0.0/s
rx-9.rx_packets: 0.0/s
rx-10.rx_packets: 0.0/s

通過這些統計,NIC 顯示 4 號 RX 隊列已經成功地傳輸大約 350Kpps。rx_nodesc_drop_cnt 是 Solarflare 特有的計數器,表明NIC發送到內核未能實現發送 450kpps。
有時候,這些數據包沒有被發送的原因不是很清晰,然而在我們這種情境下卻很清楚:4號RX隊列發送數據包到4號CPU,然而4號CPU已經忙不過來了,因為它最忙也只能讀350kpps。在htop中顯示為:

多隊列 NIC 速成課程
從歷史上看,網卡擁有單個RX隊列,用於硬體和內核之間傳遞數據包。這樣的設計有一個明顯的限制,就是不可能比單個CPU處理更多的數據包。
為了利用多核系統,NIC開始支持多個RX隊列。這種設計很簡單:每個RX隊列被附到分開的CPU上,因此,把包送到所有的RX隊列網卡可以利用所有的CPU。但是又產生了另一個問題:對於一個數據包,NIC怎麼決定把它發送到哪一個RX隊列?

用 Round-robin 的方式來平衡是不能接受的,因為這有可能導致單個連接中數據包的重排序。另一種方法是使用數據包的hash值來決定RX號碼。Hash值通常由一個元組(源IP,目標IP,源port,目標port)計算而來。這確保了從一個流產生的包將最終在完全相同的RX隊列,並且不可能在一個流中重排包。
在我們的例子中,hash值可能是這樣的:

Shell

1

RX_queue_number = hash('192.168.254.30', '192.168.254.1', 65400, 4321) % number_of_queues

多隊列 hash 演算法
Hash演算法通過ethtool配置,設置如下:

Shell

receiver$ ethtool -n eth2 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

對於IPv4 UDP數據包,NIC將hash(源 IP,目標 IP)地址。即

Shell

1

RX_queue_number = hash('192.168.254.30', '192.168.254.1') % number_of_queues

這是相當有限的,因為它忽略了埠號。很多NIC允許自定義hash。再一次,使用ethtool我們可以選擇元組(源 IP、目標 IP、源port、目標port)生成hash值。

Shell

receiver$ ethtool -N eth2 rx-flow-hash udp4 sdfn
Cannot change RX network flow hashing options: Operation not supported

不幸地是,我們的NIC不支持自定義,我們只能選用(源 IP、目的 IP) 生成hash。
NUMA性能報告
到目前為止,我們所有的數據包都流向一個RX隊列,並且一個CPU。我們可以借這個機會為基準來衡量不同CPU的性能。在我們設置為接收方的主機上有兩個單獨的處理器,每一個都是一個不同的NUMA節點。
在我們設置中,可以將單線程接收者依附到四個CPU中的一個,四個選項如下:
另一個CPU上運行接收器,但將相同的NUMA節點作為RX隊列。性能如上面我們看到的,大約是360 kpps。
將運行接收器的同一 CPU 作為RX隊列,我們可以得到大約430 kpps。但這樣也會有很高的不穩定性,如果NIC被數據包所淹沒,性能將下降到零。
當接收器運行在HT對應的處理RX隊列的CPU之上,性能是通常的一半,大約在200kpps左右。
接收器在一個不同的NUMA節點而不是RX隊列的CPU上,性能大約是330 kpps。但是數字會不太一致。
雖然運行在一個不同的NUMA節點上有10%的代價,聽起來可能不算太壞,但隨著規模的變大,問題只會變得更糟。在一些測試中,每個核只能發出250 kpps,在所有跨NUMA測試中,這種不穩定是很糟糕。跨NUMA節點的性能損失,在更高的吞吐量上更明顯。在一次測試時,發現在一個壞掉的NUMA節點上運行接收器,性能下降有4倍。
3.多接收IP
因為我們NIC上hash演算法的限制,通過RX隊列分配數據包的唯一方法是利用多個IP地址。下面是如何將數據包發到不同的目的IP:

1

sender$ taskset -c 1,2 ./udpsender 192.168.254.1:4321 192.168.254.2:4321

ethtool 證實了數據包流向了不同的 RX 隊列:

Shell

receiver$ watch 'sudo ethtool -S eth2 |grep rx'
rx-0.rx_packets: 8.0/s
rx-1.rx_packets: 0.0/s
rx-2.rx_packets: 0.0/s
rx-3.rx_packets: 355.2k/s
rx-4.rx_packets: 0.5/s
rx-5.rx_packets: 297.0k/s
rx-6.rx_packets: 0.0/s
rx-7.rx_packets: 0.5/s
rx-8.rx_packets: 0.0/s
rx-9.rx_packets: 0.0/s
rx-10.rx_packets: 0.0/s

接收部分:

Shell

receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321
0.609M pps 18.599MiB / 156.019Mb
0.657M pps 20.039MiB / 168.102Mb
0.649M pps 19.803MiB / 166.120Mb

萬歲!有兩個核忙於處理RX隊列,第三運行應用程序時,可以達到大約650 kpps !
我們可以通過發送數據到三或四個RX隊列來增加這個數值,但是很快這個應用就會有另一個瓶頸。這一次rx_nodesc_drop_cnt沒有增加,但是netstat接收到了如下錯誤:

Shell

receiver$ watch 'netstat -s --udp'
Udp:
437.0k/s packets received
0.0/s packets to unknown port received.
386.9k/s packet receive errors
0.0/s packets sent
RcvbufErrors: 123.8k/s
SndbufErrors: 0
InCsumErrors: 0

這意味著雖然NIC能夠將數據包發送到內核,但是內核不能將數據包發給應用程序。在我們的case中,只能提供440 kpps,其餘的390 kpps + 123 kpps的下降是由於應用程序接收它們不夠快。
4.多線程接收
我們需要擴展接收者應用程序。最簡單的方式是利用多線程接收,但是不管用:

Shell

sender$ taskset -c 1,2 ./udpsender 192.168.254.1:4321 192.168.254.2:4321
receiver$ taskset -c 1,2 ./udpreceiver1 0.0.0.0:4321 2
0.495M pps 15.108MiB / 126.733Mb
0.480M pps 14.636MiB / 122.775Mb
0.461M pps 14.071MiB / 118.038Mb
0.486M pps 14.820MiB / 124.322Mb

接收性能較於單個線程下降了,這是由UDP接收緩沖區那邊的鎖競爭導致的。由於兩個線程使用相同的套接字描述符,它們花費過多的時間在UDP接收緩沖區的鎖競爭。這篇論文詳細描述了這一問題。
看來使用多線程從一個描述符接收,並不是最優方案。
5. SO_REUSEPORT
幸運地是,最近有一個解決方案添加到 Linux 了 —— SO_REUSEPORT 標志位(flag)。當這個標志位設置在一個套接字描述符上時,Linux將允許許多進程綁定到相同的埠,事實上,任何數量的進程將允許綁定上去,負載也會均衡分布。
有了SO_REUSEPORT,每一個進程都有一個獨立的socket描述符。因此每一個都會擁有一個專用的UDP接收緩沖區。這樣就避免了以前遇到的競爭問題:

Shell

1
2
3
4

receiver$ taskset -c 1,2,3,4 ./udpreceiver1 0.0.0.0:4321 4 1
1.114M pps 34.007MiB / 285.271Mb
1.147M pps 34.990MiB / 293.518Mb
1.126M pps 34.374MiB / 288.354Mb

現在更加喜歡了,吞吐量很不錯嘛!
更多的調查顯示還有進一步改進的空間。即使我們開始4個接收線程,負載也會不均勻地分布:

兩個進程接收了所有的工作,而另外兩個根本沒有數據包。這是因為hash沖突,但是這次是在SO_REUSEPORT層。
結束語
我做了一些進一步的測試,完全一致的RX隊列,接收線程在單個NUMA節點可以達到1.4Mpps。在不同的NUMA節點上運行接收者會導致這個數字做多下降到1Mpps。
總之,如果你想要一個完美的性能,你需要做下面這些:
確保流量均勻分布在許多RX隊列和SO_REUSEPORT進程上。在實踐中,只要有大量的連接(或流動),負載通常是分布式的。
需要有足夠的CPU容量去從內核上獲取數據包。
To make the things harder, both RX queues and receiver processes should be on a single NUMA node.
為了使事情更加穩定,RX隊列和接收進程都應該在單個NUMA節點上。
雖然我們已經表明,在一台Linux機器上接收1Mpps在技術上是可行的,但是應用程序將不會對收到的數據包做任何實際處理——甚至連看都不看內容的流量。別太指望這樣的性能,因為對於任何實際應用並沒有太大用處。

Ⅲ linux 下用socket 文件傳輸問題(UDP)

要下班了,時間急,不寫代碼了先給你一個思路
1 實現最簡單的udp socket 模型,實現發送一個字元串。
2 實現一個簡單的打開文件,讀取文件的例子,如用fgets(),類似的函數有很多,然後再把讀取的文件內容忘另一個文件里寫(相關函數fopen(),write(),read())。
3 把上面兩個函數結合到一起,在客戶端實現打開要傳送的文件,按一定的大小讀取,讀取後調用sendto()發送到伺服器端。在伺服器端創建一個文件,然後調用recvfrom()接受客戶端發送過來的數據,向來是創建的那個文件中寫。
下面是改好的udp發送文件的例子。

伺服器端程序的編譯
gcc -o file_server  file_server
客戶端程序的編譯
gcc -o file_client  file_client.c
伺服器程序和客戶端程應當分別運行在2台計算機上.
伺服器端程序的運行,在一個計算機的終端執行
./file_server
客戶端程序的運行,在另一個計算機的終端中執行
./file_client  運行伺服器程序的計算機的IP地址
根據提示輸入要傳輸的伺服器上的文件,該文件在伺服器的運行目錄上
在實際編程和測試中,可以用2個終端代替2個計算機,這樣就可以在一台計算機上測試網路程序,
伺服器端程序的運行,在一個終端執行
./file_server
客戶端程序的運行,在另一個終端中執行
./file_client  127.0.0.1
說明: 任何計算機都可以通過127.0.0.1訪問自己. 也可以用計算機的實際IP地址代替127.0.0.1
//////////////////////////////////////////////////////////////////////////////////////
// file_server.c  文件傳輸順序伺服器示例
//////////////////////////////////////////////////////////////////////////////////////
//本文件是伺服器的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
*/
#define HELLO_WORLD_SERVER_PORT    6666
#define LENGTH_OF_LISTEN_QUEUE  20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    //設置一個socket地址結構server_addr,代表伺服器internet地址, 埠
    struct sockaddr_in server_addr, pcliaddr;
    bzero(&server_addr,sizeof(server_addr)); //把一段內存區的內容全部設置為0
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

    //創建用於internet的據報套接字(UDPt,用server_socket代表伺服器socket

// 創建數據報套接字(UDP)
    int server_socket = socket(PF_INET,SOCK_DGRAM,0);
    if( server_socket < 0)
    {
        printf("Create Socket Failed!");
        exit(1);
    }
   
    //把socket和socket地址結構聯系起來
    if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    {
        printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
        exit(1);
    }
   
    while (1) //伺服器端要一直運行
    {
        //定義客戶端的socket地址結構client_addr
        struct sockaddr_in client_addr;
        socklen_t n = sizeof(client_addr) ;
int length;

        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        length = recvfrom(new_server_socket,buffer,BUFFER_SIZE,0,&pcliaddr,&n);
        if (length < 0)
        {
            printf("Server Recieve Data Failed!\n");
            break;
        }
        char file_name[FILE_NAME_MAX_SIZE+1];
        bzero(file_name, FILE_NAME_MAX_SIZE+1);
        strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
//        int fp = open(file_name, O_RDONLY);
//        if( fp < 0 )
        FILE * fp = fopen(file_name,"r");
        if(NULL == fp )
        {
            printf("File:\t%s Not Found\n", file_name);
        }
        else
        {
            bzero(buffer, BUFFER_SIZE);
            int file_block_length = 0;
//            while( (file_block_length = read(fp,buffer,BUFFER_SIZE))>0)
            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
            {
                printf("file_block_length = %d\n",file_block_length);
                //發送buffer中的字元串到new_server_socket,實際是給客戶端
                if(send(new_server_socket,buffer,file_block_length,0)<0)
                {
                    printf("Send File:\t%s Failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
//            close(fp);
            fclose(fp);
            printf("File:\t%s Transfer Finished\n",file_name);
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////
// file_client.c  文件傳輸客戶端程序示例
//////////////////////////////////////////////////////////////////////////////////////
//本文件是客戶機的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
*/

#define HELLO_WORLD_SERVER_PORT    6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: ./%s ServerIPAddress\n",argv[0]);
        exit(1);
    }

    //設置一個socket地址結構client_addr,代表客戶機internet地址, 埠
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr)); //把一段內存區的內容全部設置為0
    client_addr.sin_family = AF_INET;    //internet協議族
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自動獲取本機地址
    client_addr.sin_port = htons(0);    //0表示讓系統自動分配一個空閑埠
    //創建用於internet的流協議(TCP)socket,用client_socket代表客戶機socket
    int client_socket = socket(AF_INET,SOCK_DGRAM,0);
    if( client_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }

    //設置一個socket地址結構server_addr,代表伺服器的internet地址, 埠
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //伺服器的IP地址來自程序的參數
    {
        printf("Server IP Address Error!\n");
        exit(1);
    }
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t server_addr_length = sizeof(server_addr);

    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Server:\t");
    scanf("%s", file_name);
   
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
    //向伺服器發送buffer中的數據
     socklen_t n = sizeof(server_addr) ;
    sendto(client_socket,buffer,BUFFER_SIZE,0,(struct sockaddr*)&server_addr,n);

//    int fp = open(file_name, O_WRONLY|O_CREAT);
//    if( fp < 0 )
    FILE * fp = fopen(file_name,"w");
    if(NULL == fp )
    {
        printf("File:\t%s Can Not Open To Write\n", file_name);
        exit(1);
    }
   
    //從伺服器接收數據到buffer中
    bzero(buffer,BUFFER_SIZE);
    int length = 0;
    while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
    {
        if(length < 0)
        {
            printf("Recieve Data From Server %s Failed!\n", argv[1]);
            break;
        }
//        int write_length = write(fp, buffer,length);
        int write_length = fwrite(buffer,sizeof(char),length,fp);
        if (write_length<length)
        {
            printf("File:\t%s Write Failed\n", file_name);
            break;
        }
        bzero(buffer,BUFFER_SIZE);   
    }
    printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
    return 0;
}

Ⅳ 使用recvfrom接收UDP包在Windows和Linux平台的不同表現

1 UDP接收原理
操作系統的UDP接收流程如下:收到一個UDP包後,驗證沒有錯誤後,放入一個包隊列中,隊列中的每一個元素就是一個完整的UDP包。當應用程序通過recvfrom()讀取時,OS把相應的一個完整UDP包取出,然後拷貝到用戶提供的內存中,物理用戶提供的內存大小是多少,OS都會完整取出一個UDP包。如果用戶提供的內存小於這個UDP包的大小,那麼在填充慢內存後,UDP包剩餘的部分就會被丟棄,以後再也無法取回。

這與TCP接收完全不同,TCP沒有完整包的概念,也沒有邊界,OS只會取出用戶要求的大小,剩餘的仍然保留在OS中,下次還可以繼續取出。

socket編程雖然是事實上的標准,而且不同平台提供的介面函數也非常類似,但畢竟它不存在嚴格的標准。所以各個平台的實現也不完全兼容。下面就從recvfrom()這個函數看看Window平台和Linux平台的不同。
2 Windows平台的表現
先看頭文件中的聲明:
[cpp] view plain在CODE上查看代碼片派生到我的代碼片
int
WSAAPI
recvfrom(
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen
);

再看MSDN說明:
If the datagram or message is larger than the buffer specified, the buffer is filled with the first part of the datagram, and recvfrom generates the error WSAEMSGSIZE. For unreliable protocols (for example, UDP) the excess data is lost.
可以看出,buf大小小於UDP包大小的時候,recvfrom()會返回-1,並設置錯誤WSAEMSGSIZE。
實際編程測試驗證確實是這樣的表現。
3 Linux平台的表現
先看頭文件中的聲明:
[cpp] view plain在CODE上查看代碼片派生到我的代碼片
__extern_always_inline ssize_t
recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags,
__SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len)

可以看出與Windows平台的函數原型相同。但是在其man手冊里,沒有看到UDP包大於接收緩沖區情況的特殊說明。
寫代碼測試表明,buf小於UDP包大小的時候,recvfrom()仍然返回復制到緩沖區的位元組數,調用者無法得知UDP包被截斷的情況。
4 寫代碼注意事項
UDP包最大是多大呢?UDP頭部大小欄位佔16位元組,所以理論上是65535個位元組大小。但是UDP如果是通過IP(大多數情況)來傳送,由於UDP本身不支持分片,所以一個UDP包只能通過一個IP包來傳送,一個IP包大大小理論上也是用16位元組表示,這樣UDP最大大小就是(65535-IP頭部)。

而現實中如果IP包大小大於底層鏈路層幀的最大數據區大小,則必須對IP包進行分片傳送。分片會嚴重影響傳送效率,而且增大不穩定性,所以實際的網路程序發送的IP包都封裝到單一的鏈路層幀中,從而避免分片。問題是鏈路層幀是多大呢?答案是不一定,因為不同的物理網路的幀大小不一樣,如乙太網是1500位元組,但是其他物理網路可能更小,Internet上的有個最小的限制,那就是576位元組。如果UDP程序運行在只運行在乙太網中,那麼為了避免IP分片,可以採用的最大大小為(1500-20-8)=1472位元組。如果UDP程序需要運行在Internet上,那麼建議最大大小為(576-20-8)=548位元組。

上面是實踐中的最佳UDP大小,但是並不是所有程序都採用上述經驗,所以對於接收緩沖區的大小也就沒有一個標准,而是取決於應用程序設計者本身。雖然對於Windows平台,recvfrom()能夠提示調用者buf過小的問題,但是即使得到了這個錯誤,包還是被丟棄了。所以在接收UDP包時,一定要事先了解應用層設計的最大UDP包大小,然後按照最大值開辟接收緩沖區。

Ⅳ linux查看本地一個udp埠有沒有接收到數據包

使用如下命令: tcpmp udp port 200

熱點內容
什麼樣的配置能玩地平線4 發布:2025-01-31 22:44:05 瀏覽:241
python正則表達式符號 發布:2025-01-31 22:43:50 瀏覽:391
androidmime 發布:2025-01-31 22:34:44 瀏覽:782
ftp和http的中文含義是 發布:2025-01-31 22:33:48 瀏覽:402
sqlite3存儲圖片 發布:2025-01-31 22:27:14 瀏覽:162
sqlserverphp 發布:2025-01-31 22:22:55 瀏覽:877
曲馬多存儲 發布:2025-01-31 22:22:52 瀏覽:538
緩存兒歌 發布:2025-01-31 22:21:26 瀏覽:528
學java有發展嗎 發布:2025-01-31 21:44:45 瀏覽:569
HBX編程 發布:2025-01-31 21:39:26 瀏覽:161