使用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 }
服务端代码:
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 }
注意:延迟时间为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 }
服务端代码:
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 }
注意: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测量网络延迟的主要内容,如果未能解决你的问题,请参考以下文章