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