摘要
本文記錄通過數據報套接字來檢測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);
}