摘要
本文记录通过数据报套接字来检测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; //2sint 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; //2sint 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);}