UDP數據包的延遲及丟包檢測(C++)

摘要

本文記錄通過數據報套接字來檢測UDP數據包的延遲和丟包的思路和簡單的代碼實現。

思路

UDP協議及用戶數據報協議在傳輸層提供了無連接、不可靠的傳輸服務,端到端的延遲以及丟包率是反應當前網絡環境好壞的重要評價標準。Ping檢測延遲的方式是:發送端發送一個ICMP包給接收端,接收端接收到ICMP包之後向發送端迴應一個包,發送端可以計算出往返時間(RTT),本文通過套接字使用類似於Ping的思路來計算RTT來反映延遲的大小,可以多次發包根據多次的結果來計算一個平均的RTT;丟包率可以通過指定發送端發包的數量,然後在接收端統計接收成功的數量,就可以計算出當前丟包率。

說明

  • 數據報套接字(SOCK-DGRAM)在傳輸層使用的UDP協議,所以在創建socket的時候一定要指定使用SOCK_DGRAM類型的套接字,這樣系統在封裝數據包的時候才使用的是UDP協議。
  • 因爲不同設備的網絡數據發送能力和接受能力都有限,發送端每次發送內容的長度也就是數據包的大小,以及發送速率都是影響接收端延遲和丟包率的因素;因此將發送數據大小(bytes)、發送數據包數量、發送間隔(ms)都作爲程序的參數,根據不用的應用場景指定對應的參數,得到的結果會有一定的參考價值。

代碼實現

加入頭文件

代碼實在windows環境下寫的,所以發送端和接收端調用函數之前都需要加入頭文件和預處理指令

#include <iostream>
#include <ctime>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")  //加載 ws2_32.dll

發送端函數

//參數分別爲接收端的IP地址、端口以及本次發包的數量和每個包的內容長度
void udp_delay_detect_client(const char* ip,int port,int packetSize,int packetNum)
{
	WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    //創建UDP套接字
    SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0);

    //服務器地址信息
    sockaddr_in servAddr;
    memset(&servAddr, 0, sizeof(servAddr));  //每個字節都用0填充
    servAddr.sin_family = PF_INET;
    servAddr.sin_addr.s_addr = inet_addr(ip);
    servAddr.sin_port = htons(port);
    //不斷獲取用戶輸入併發送給服務器,然後接受服務器數據
    SOCKADDR clntAddr;  //客戶端地址信息
    int nSize = sizeof(SOCKADDR);
    char bufSend[packetSize+1];
    memset(bufSend,'a',sizeof(bufSend));
    bufSend[packetSize]='\0';

    char bufRecv[packetSize+1];
	//先發一個包告訴接收端要開始發包了
    sendto(sock,bufSend,strlen(bufSend),0,(struct sockaddr*)&servAddr, sizeof(servAddr));		

    int send_count=packetNum;
    int recv_count=0;

    double sum_delay=0;
    double sum_jitter=0;
    double last_delay;

    int recv_miss_count=0;

    //設置超時等待
    int timeout = 2000; //2s
    int ret=setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));

    while(send_count--){
        sendto(sock, bufSend, strlen(bufSend), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));
        clock_t start=clock();
        int strLen = recvfrom(sock, bufRecv, packetSize, 0, &clntAddr, &nSize);
        if(strLen<1)
        {
            continue;
        }
        clock_t end=clock();
        recv_count++;
        double delay=(double)(end-start)*1000/CLOCKS_PER_SEC;
        double jitter;
        if(send_count==packetNum){jitter=0,last_delay=delay;continue;}
        jitter=fabs(delay-last_delay);
        sum_delay+=delay;
        sum_jitter+=jitter;
    }

    double delay=sum_delay/packetNum;
    double jitter=sum_jitter/packetNum;
    cout<<"RRT:"<<delay<<"ms jitter:"<<jitter<<"ms packet_loss_rate:"<<1-(double)recv_count/packetNum<<endl;
    closesocket(sock);
}

接收端函數
void udp_delay_detect_server(int port)		//指定端口
{
	WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    //創建套接字
    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
    //綁定套接字
    sockaddr_in servAddr;
    memset(&servAddr, 0, sizeof(servAddr));  //每個字節都用0填充
    servAddr.sin_family = PF_INET;  //使用IPv4地址
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自動獲取IP地址
    servAddr.sin_port = htons(port);  //端口
    bind(sock, (SOCKADDR*)&servAddr, sizeof(SOCKADDR));
    //接收客戶端請求
    SOCKADDR clntAddr;  //客戶端地址信息
    int nSize = sizeof(SOCKADDR);
    char buffer[BUF_SIZE];  //緩衝區
    
    recvfrom(sock,buffer,BUF_SIZE,0,&clntAddr,&nSize);			//接受發送端的開始發送的信號
    
    int recv_count=0;
    //設置超時等待
    int timeout = 2000; //2s
    int ret=setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));

    int recv_miss_count=0;
    while(1){
        int strLen = recvfrom(sock, buffer, BUF_SIZE, 0, &clntAddr, &nSize);
        if(strLen>1)              //驗證接受字符串的長度
        {
            sendto(sock, buffer, strLen, 0, &clntAddr, nSize);
            recv_count++;
        }
        else{
            recv_miss_count++;
            cout<<"no packet recieve!"<<endl;
            if(recv_miss_count==3)				//超過三次超時等待則認爲接收完畢
                break;
        }
    }
    cout<<"packet_recv_count:"<<recv_count<<endl;
    closesocket(sock);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章