使用UDP和TCP测量网络延迟

Posted tangxin-blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用UDP和TCP测量网络延迟相关的知识,希望对你有一定的参考价值。

一、原理

客户端发送大小随机的数据包,附带上发送时间,然后接受服务端的返回包,计算往返的时间差。服务端收到包之后,立即原样返回给客户端。

包的大小在[28, 10240]之间随机

发送5000个测试包

服务器在腾讯云,客户端在本地办公机。

测量结果使用Excel绘制成图表,x轴表示包的大小(byte),y轴表示延迟时间(us)

二、使用UDP协议

客户端代码:

技术图片
  1 #ifdef WIN32
  2 #include <WinSock2.h>
  3 #include <windows.h>
  4 #pragma comment(lib,"ws2_32.lib")
  5 typedef int socklen_t;
  6 #else
  7 #include <sys/time.h>
  8 #include <unistd.h>
  9 #include <string.h>
 10 #include <sys/select.h>
 11 #include <sys/socket.h>
 12 #include <arpa/inet.h>
 13 #include <sys/types.h>
 14 #include <netinet/in.h>
 15 #include <fcntl.h>
 16 typedef int SOCKET;
 17 #endif
 18 
 19 #include <iostream>
 20 #include <ctime>
 21 #include <cstdlib>
 22 #include <cstdio>
 23 #include <vector>
 24 
 25 #define MAX_BUFFER_LEN 10240
 26 #define MAX_PACK_COUNT 5000
 27 
 28 struct Pack
 29 {
 30     int id = 0, len = 0;
 31     int64_t time = 0;
 32 };
 33 
 34 int64_t GetSysTimeMicros()
 35 {
 36 #ifdef _WIN32
 37     // 从1601年1月1日0:0:0:000到1970年1月1日0:0:0:000的时间(单位100ns)
 38 #define EPOCHFILETIME   (116444736000000000UL)
 39     FILETIME ft;
 40     LARGE_INTEGER li;
 41     int64_t tt = 0;
 42     GetSystemTimeAsFileTime(&ft);
 43     li.LowPart = ft.dwLowDateTime;
 44     li.HighPart = ft.dwHighDateTime;    // 从1970年1月1日0:0:0:000到现在的微秒数(UTC时间)
 45     tt = (li.QuadPart - EPOCHFILETIME) / 10;
 46     return tt;
 47 #else
 48     timeval tv;
 49     gettimeofday(&tv, 0);
 50     return (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;
 51 #endif // _WIN32    return 0;
 52 }
 53 
 54 int SetNonBlocking(SOCKET fd)
 55 {
 56 #ifdef WIN32
 57     unsigned long ul = 1;
 58     int ret = ioctlsocket(fd, FIONBIO, (unsigned long *)&ul);
 59     if(ret == SOCKET_ERROR)
 60     {
 61         std::cerr << "ioctlsocket fail" << std::endl;
 62         return -1;
 63     }
 64 #else
 65     int flag = fcntl(fd, F_GETFL, 0);
 66     if(flag < 0)
 67     {
 68         std::cerr << "fcntl F_GETFL fail" << std::endl;
 69         return -1;
 70     }
 71     if(fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0)
 72     {
 73         std::cerr << "fcntl F_SETFL fail" << std::endl;
 74         return -2;
 75     }
 76 #endif
 77     return 0;
 78 }
 79 
 80 int main(int argc, char *argv[])
 81 {
 82     int ret = 0;
 83 #ifdef WIN32
 84     WSADATA wsaData;
 85     ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
 86 #endif
 87 
 88     SOCKET fd = socket(AF_INET, SOCK_DGRAM, 0);
 89 
 90     sockaddr_in self_addr;
 91     self_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 92     self_addr.sin_family = AF_INET;
 93     self_addr.sin_port = htons(9999);
 94 
 95     bind(fd, (sockaddr*)&self_addr, sizeof(sockaddr));
 96 
 97     // 设置为非阻塞模式
 98     ret = SetNonBlocking(fd);
 99     if(ret != 0)
100     {
101         std::cerr << "SetNonBlocking Failed." << std::endl;
102         return -1;
103     }
104 
105     sockaddr_in peer_addr;
106     peer_addr.sin_addr.s_addr = inet_addr("123.207.178.81");
107     peer_addr.sin_family = AF_INET;
108     peer_addr.sin_port = htons(8888);
109 
110     char buf[MAX_BUFFER_LEN] = { 0 };
111     socklen_t len = sizeof(sockaddr);
112 
113     srand((unsigned int)time(NULL));
114 
115     const int MINI_PACK_LEN = sizeof(Pack);
116 
117     int pack_counter = 0;
118     Pack pack_tmp;
119     std::vector<Pack> pack_array;
120     pack_array.resize(MAX_PACK_COUNT);
121 
122     fd_set read_set;
123 
124     // 每次收包最大等待100ms
125     struct timeval tv = { 0, 100000 };
126 
127     while(true)
128     {
129         if(pack_counter < MAX_PACK_COUNT)
130         {
131             // 构造随机长度的包,大小为[MINI_PACK_LEN, MAX_BUFFER_LEN]
132             int pack_len = rand() % (MAX_BUFFER_LEN - MINI_PACK_LEN) + MINI_PACK_LEN;
133             Pack &pack = pack_array[pack_counter];
134             pack.id = pack_counter;
135             pack.len = pack_len;
136             pack.time = GetSysTimeMicros();
137 
138             memcpy(buf, &pack, sizeof(Pack));
139 
140             int sent_len = sendto(fd, buf, pack_len, 0, (sockaddr*)&peer_addr, sizeof(sockaddr));
141             if(sent_len < 0)
142             {
143                 std::cout << "sendto failed.pack_len = " << pack_len << std::endl;
144                 break;
145             }
146             //printf("sent:%-5d | %-5d | %-32lld
", pack.id, pack.len, pack.time);
147             ++pack_counter;
148         }
149 
150         FD_ZERO(&read_set);
151         FD_SET(fd, &read_set);
152         ret = select(int(fd + 1), &read_set, NULL, NULL, &tv);
153         if(ret > 0 && FD_ISSET(fd, &read_set))
154         {
155             // 多次接收,保证收完
156             while(true)
157             {
158                 int recv_len = recvfrom(fd, buf, MAX_BUFFER_LEN, 0, (sockaddr*)&peer_addr, &len);
159                 if(recv_len < 0)
160                 {
161                     break;
162                 }
163                 memcpy(&pack_tmp, buf, sizeof(Pack));
164                 if(pack_tmp.id < 0 || pack_tmp.id >= MAX_PACK_COUNT || pack_tmp.len != recv_len)
165                 {
166                     std::cerr << "pack_tmp error." << std::endl;
167                     return -2;;
168                 }
169                 Pack &pack = pack_array[pack_tmp.id];
170                 pack.time = GetSysTimeMicros() - pack.time;
171                 //printf("%d	%lld
", pack.len, pack.time);
172             }
173         }
174 
175         static int64_t sent_finish_time = 0;
176         if(pack_counter == MAX_PACK_COUNT)
177         {
178             if(sent_finish_time == 0)
179             {
180                 sent_finish_time = GetSysTimeMicros();
181             }
182             else
183             {
184                 if(GetSysTimeMicros() - sent_finish_time > 2000000)
185                 {
186                     break;
187                 }
188             }
189         }
190     }
191 
192     int lose_pack_counter = 0;
193     for(Pack & pack : pack_array)
194     {
195         if(pack.time > 20000000)
196         {
197             ++lose_pack_counter;
198             pack.time = 0;
199         }
200         std::cout << pack.len << "	" << pack.time << std::endl;
201     }
202     std::cout << (float)lose_pack_counter / MAX_PACK_COUNT << std::endl;
203 
204 #ifdef WIN32
205     closesocket(fd);
206     WSACleanup();
207 #else
208     close(fd);
209 #endif
210     return 0;
211 }
View Code

服务端代码:

技术图片
 1 #ifdef WIN32
 2 #include <windows.h>
 3 #pragma comment(lib,"ws2_32.lib")
 4 typedef int socklen_t;
 5 #else
 6 #include <sys/socket.h>
 7 #include <arpa/inet.h>
 8 #include <netinet/in.h>
 9 #include <unistd.h>
10 #include <string.h>
11 typedef int SOCKET;
12 #endif
13 
14 #include <iostream>
15 #include <cstdio>
16 
17 #define MAX_BUFFER_LEN 10240
18 
19 int main(int argc, char *argv[])
20 {
21     int ret = 0;
22 #ifdef WIN32
23     WSADATA wsaData;
24     ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
25 #endif
26 
27     SOCKET fd = socket(AF_INET, SOCK_DGRAM, 0);
28 
29     sockaddr_in self_addr;
30     self_addr.sin_addr.s_addr = htonl(INADDR_ANY);
31     self_addr.sin_family = AF_INET;
32     self_addr.sin_port = htons(8888);
33 
34     bind(fd, (sockaddr*)&self_addr, sizeof(sockaddr));
35 
36     sockaddr_in peer_addr;
37     socklen_t len = sizeof(sockaddr);
38     char recv_buf[MAX_BUFFER_LEN] = { 0 };
39 
40     while(true)
41     {
42         ret = recvfrom(fd, recv_buf, MAX_BUFFER_LEN, 0, (sockaddr*)&peer_addr, &len);
43         if(ret < 0)
44         {
45             std::cout << "recvfrom failed" << std::endl;
46             break;
47         }
48         sendto(fd, recv_buf, ret, 0, (sockaddr*)&peer_addr, sizeof(sockaddr));
49     }
50 
51 #ifdef WIN32
52     closesocket(fd);
53     WSACleanup();
54 #else
55     close(fd);
56 #endif
57     return 0;
58 }
View Code

 技术图片

注意:延迟时间为0(位于x轴上) 的点,表示包丢了,因为UDP是不可靠的传输,丢包很常见。

测试结果:平均延时(丢包不参与计算) 2.439ms,丢包率22.84%,服务端网络流量约为1.46M/s。

从图表中可以看出,大约以x=1500为界,左边的包延迟较高,但是丢包率较低;右边的包延迟较低,但是丢包率很高。而且随着x增加,丢包率越来越高。

分析:

1. 估计本地网络的MTU是1500,大小超过这个值的包,在数据链路层会被分包发送,分包导致丢包率增加,所以左边的包丢包率低,右边的包延迟很高。

2. 为啥小于MTU的包的延迟会高一点呢?暂时还不是很明白。。。

二、使用TCP协议

客户端代码:

技术图片
  1 #ifdef WIN32
  2 #include <WinSock2.h>
  3 #include <windows.h>
  4 #pragma comment(lib,"ws2_32.lib")
  5 typedef int socklen_t;
  6 #else
  7 #include <sys/time.h>
  8 #include <unistd.h>
  9 #include <string.h>
 10 #include <sys/select.h>
 11 #include <sys/socket.h>
 12 #include <arpa/inet.h>
 13 #include <sys/types.h>
 14 #include <netinet/in.h>
 15 #include <fcntl.h>
 16 typedef int SOCKET;
 17 #endif
 18 
 19 #include <iostream>
 20 #include <ctime>
 21 #include <cstdlib>
 22 #include <cstdio>
 23 #include <vector>
 24 
 25 #define MAX_BUFFER_LEN 10240
 26 #define MAX_PACK_COUNT 5000
 27 
 28 struct Pack
 29 {
 30     int id = 0, len = 0;
 31     int64_t time = 0;
 32 };
 33 
 34 int64_t GetSysTimeMicros()
 35 {
 36 #ifdef _WIN32
 37     // 从1601年1月1日0:0:0:000到1970年1月1日0:0:0:000的时间(单位100ns)
 38 #define EPOCHFILETIME   (116444736000000000UL)
 39     FILETIME ft;
 40     LARGE_INTEGER li;
 41     int64_t tt = 0;
 42     GetSystemTimeAsFileTime(&ft);
 43     li.LowPart = ft.dwLowDateTime;
 44     li.HighPart = ft.dwHighDateTime;    // 从1970年1月1日0:0:0:000到现在的微秒数(UTC时间)
 45     tt = (li.QuadPart - EPOCHFILETIME) / 10;
 46     return tt;
 47 #else
 48     timeval tv;
 49     gettimeofday(&tv, 0);
 50     return (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;
 51 #endif // _WIN32    return 0;
 52 }
 53 
 54 int SetNonBlocking(SOCKET fd)
 55 {
 56 #ifdef WIN32
 57     unsigned long ul = 1;
 58     int ret = ioctlsocket(fd, FIONBIO, (unsigned long *)&ul);
 59     if(ret == SOCKET_ERROR)
 60     {
 61         std::cerr << "ioctlsocket fail" << std::endl;
 62         return -1;
 63     }
 64 #else
 65     int flag = fcntl(fd, F_GETFL, 0);
 66     if(flag < 0)
 67     {
 68         std::cerr << "fcntl F_GETFL fail" << std::endl;
 69         return -1;
 70     }
 71     if(fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0)
 72     {
 73         std::cerr << "fcntl F_SETFL fail" << std::endl;
 74         return -2;
 75     }
 76 #endif
 77     return 0;
 78 }
 79 
 80 int main(int argc, char *argv[])
 81 {
 82     int ret = 0;
 83 #ifdef WIN32
 84     WSADATA wsaData;
 85     ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
 86 #endif
 87 
 88     sockaddr_in peer_addr;
 89     peer_addr.sin_addr.s_addr = inet_addr("123.207.178.81");
 90     //peer_addr.sin_addr.s_addr = inet_addr("10.8.24.111");
 91     peer_addr.sin_family = AF_INET;
 92     peer_addr.sin_port = htons(8888);
 93 
 94     SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
 95 
 96     ret = connect(fd, (sockaddr*)&peer_addr, sizeof(peer_addr));
 97     if(ret != 0)
 98     {
 99         std::cerr << "connect error." << std::endl;
100         return -1;
101     }
102 
103     // 设置为非阻塞模式
104     ret = SetNonBlocking(fd);
105     if(ret != 0)
106     {
107         std::cerr << "SetNonBlocking Failed." << std::endl;
108         return -1;
109     }
110 
111 
112 
113     char buf[MAX_BUFFER_LEN] = { 0 };
114     socklen_t len = sizeof(sockaddr);
115 
116     srand((unsigned int)time(NULL));
117 
118     const int MINI_PACK_LEN = sizeof(Pack);
119 
120     int pack_counter = 0;
121     Pack pack_tmp;
122     std::vector<Pack> pack_array;
123     pack_array.resize(MAX_PACK_COUNT);
124 
125     fd_set read_set;
126 
127     // 每次收包最大等待100ms
128     struct timeval tv = { 0, 100000 };
129 
130     while(true)
131     {
132         if(pack_counter < MAX_PACK_COUNT)
133         {
134             // 构造随机长度的包,大小为[MINI_PACK_LEN, MAX_BUFFER_LEN]
135             int pack_len = rand() % (MAX_BUFFER_LEN - MINI_PACK_LEN) + MINI_PACK_LEN;
136             Pack &pack = pack_array[pack_counter];
137             pack.id = pack_counter;
138             pack.len = pack_len;
139             pack.time = GetSysTimeMicros();
140 
141             memcpy(buf, &pack, sizeof(Pack));
142 
143             int sent_len = send(fd, buf, pack_len, 0);
144             if(sent_len < 0)
145             {
146                 std::cout << "sendto failed.pack_len = " << pack_len << std::endl;
147                 break;
148             }
149             //printf("sent:%-5d | %-5d | %-32lld
", pack.id, pack.len, pack.time);
150             ++pack_counter;
151         }
152 
153         FD_ZERO(&read_set);
154         FD_SET(fd, &read_set);
155         ret = select(int(fd + 1), &read_set, NULL, NULL, &tv);
156         if(ret > 0 && FD_ISSET(fd, &read_set))
157         {
158             // 多次接收,保证收完
159             while(true)
160             {
161                 ret = recv(fd, (char*)&pack_tmp, sizeof(Pack), MSG_PEEK);
162                 if(ret != sizeof(Pack))
163                 {
164                     break;
165                 }
166                 ret = recv(fd, buf, pack_tmp.len, MSG_PEEK);
167                 if(ret == pack_tmp.len)
168                 {
169                     ret = recv(fd, buf, pack_tmp.len, 0);
170                     if(ret < 0)
171                     {
172                         std::cout << "recv failed" << std::endl;
173                         break;
174                     }
175                     if(pack_tmp.id < 0 || pack_tmp.id >= MAX_PACK_COUNT || pack_tmp.len != ret)
176                     {
177                         std::cerr << "pack_tmp error." << std::endl;
178                         return -2;;
179                     }
180                     Pack &pack = pack_array[pack_tmp.id];
181                     pack.time = GetSysTimeMicros() - pack.time;
182                     //printf("recv:%-5d | %-5d | %-32lld
", pack.id, pack.len, pack.time);
183                 }
184 
185             }
186         }
187 
188         static int64_t sent_finish_time = 0;
189         if(pack_counter == MAX_PACK_COUNT)
190         {
191             if(sent_finish_time == 0)
192             {
193                 sent_finish_time = GetSysTimeMicros();
194             }
195             else
196             {
197                 if(GetSysTimeMicros() - sent_finish_time > 2000000)
198                 {
199                     break;
200                 }
201             }
202         }
203     }
204 
205     int lose_pack_counter = 0;
206     for(Pack & pack : pack_array)
207     {
208         if(pack.time > 20000000)
209         {
210             ++lose_pack_counter;
211             pack.time = 0;
212         }
213         std::cout << pack.len << "	" << pack.time << std::endl;
214     }
215     std::cout << (float)lose_pack_counter / MAX_PACK_COUNT << std::endl;
216 
217 #ifdef WIN32
218     closesocket(fd);
219     WSACleanup();
220 #else
221     close(fd);
222 #endif
223     return 0;
224 }
View Code

服务端代码:

技术图片
 1 #ifdef WIN32
 2 #include <windows.h>
 3 #pragma comment(lib,"ws2_32.lib")
 4 typedef int socklen_t;
 5 #else
 6 #include <sys/socket.h>
 7 #include <arpa/inet.h>
 8 #include <netinet/in.h>
 9 #include <unistd.h>
10 #include <string.h>
11 typedef int SOCKET;
12 #endif
13 
14 #include <iostream>
15 #include <cstdio>
16 
17 #define MAX_BUFFER_LEN 10240
18 
19 struct Pack
20 {
21     int id = 0, len = 0;
22     int64_t time = 0;
23 };
24 
25 int main(int argc, char *argv[])
26 {
27     int ret = 0;
28 #ifdef WIN32
29     WSADATA wsaData;
30     ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
31 #endif
32 
33     SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
34 
35     sockaddr_in self_addr;
36     self_addr.sin_addr.s_addr = htonl(INADDR_ANY);
37     self_addr.sin_family = AF_INET;
38     self_addr.sin_port = htons(8888);
39 
40     bind(fd, (sockaddr*)&self_addr, sizeof(sockaddr));
41 
42     listen(fd, SOMAXCONN);
43 
44     sockaddr_in peer_addr;
45     socklen_t addrlen = sizeof(sockaddr);
46 
47     SOCKET new_fd = accept(fd, (sockaddr*)&peer_addr, &addrlen);
48     std::cout << "accept:" << peer_addr.sin_addr.s_addr << ":" << peer_addr.sin_port << std::endl;
49 
50     char recv_buf[MAX_BUFFER_LEN] = { 0 };
51 
52     Pack pack;
53 
54     while(true)
55     {
56         // 简单处理一下粘包问题
57         ret = recv(new_fd, (char*)&pack, sizeof(Pack), MSG_PEEK);
58         if(ret == sizeof(Pack))
59         {
60             ret = recv(new_fd, recv_buf, pack.len, MSG_PEEK);
61             if(ret == pack.len)
62             {
63                 ret = recv(new_fd, recv_buf, pack.len, 0);
64                 if(ret < 0)
65                 {
66                     std::cout << "recv failed" << std::endl;
67                     break;
68                 }
69                 //printf("recv:%-5d | %-5d | %-32lld
", pack.id, pack.len, pack.time);
70                 ret = send(new_fd, recv_buf, ret, 0);
71                 if(ret < 0)
72                 {
73                     std::cout << "recv failed" << std::endl;
74                     break;
75                 }
76             }
77         }
78 
79     }
80 
81 #ifdef WIN32
82     closesocket(fd);
83     WSACleanup();
84 #else
85     close(fd);
86 #endif
87     return 0;
88 }
View Code

技术图片

 

 

注意:TCP可能会发生粘包问题,需要手动处理粘包。

测试结果:平均延时86.724ms,延迟中位数9.023ms,丢包率0(TCP使命必达),服务端网络流量约为1.1M/s。

如果除去延迟超过200ms的奇异点,平均延时为:15.114ms。

从图表中看出:

1. 总的来看TCP的网络延时比较平均,和包的大小没有太大关系。

2. 在延时较低的点中,出现了一些分层特征,而不是完全随机分布。

3. 会出现一些延迟特别高的奇异点,这些点的值远远大于延迟中位数。 

分析:

1. TCP采用流的方式传输数据,内部没有包的概念,所以包的大小对TCP的延迟没有太大影响。

2. 由于TCP有重传机制,每发生一次重传就会增加一定的网络延时,所以在延时较低的点中,出现了一些分层特征,而并没有完全随机。而且包越大,在底层被分的包就越多,丢包的概率就越大,高延迟的包占比就越高。

3. 如果某一瞬间网络环境很差,丢包率很高,TCP就会尝试不停的重传,所以产生一些延迟很高的点。而且TCP是有序的,这些奇异点会直接影响后面的数据收发,导致TCP的延迟很不稳定。

 

以上是关于使用UDP和TCP测量网络延迟的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C 对 TCP 和 UDP 进行基准测试?

网络性能测试工具iperf详细使用图文教程zz

网络测试

iperf网络测试工具使用方法

Iperf网络检测工具

iperf命令