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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章