This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

1. Background

In the mobile application development scenario, it is inevitable to deal with the network. Sometimes when a network request fails, we want to know the quality of the network; Sometimes it is necessary to explicitly inform the user of the current network quality (such as the real-time display latency of the game scene). Network monitoring is inseparable from the classic TCP/IP model. Hierarchical statistics of network time consumption based on the model help us to understand the current network quality more clearly.

In the TCP/IP reference model, it is difficult to count the physical layer at the network level. The network layer has Ping, the transport layer has Socket interfaces provided by the system, and the application layer has HTTP and RTMP protocols. This article describes the ping tool, DNS resolution time, TCP connection time, and HTTP establishment time.

2. ping

Ping is based on ICMP at the network layer and sends ICMP echo request packets. Let’s take a look at ICMP first.

2.1 Introduction to ICMP (Internet Control Message Protocol

ICMP, a component of the IP layer, transmits error packets and other information requiring attention. ICMP packets are usually used by IP layer or higher layer protocols (TCP or UDP). See RFC 792[Posterl 1981b] for the formal specification of ICMP, ICMP is encapsulated inside an IP packet in the format of a 20-byte IP header +ICMP message. ICMP packet format is as follows:

The first four bytes of all packets are the same, but the remaining bytes are different. The type field can have up to 15 different values to describe a particular type of ICMP message, and some CIMP messages use code field values to further describe different conditions. The checksum field overwrites the entire ICMP packet.

Different types are determined by the type field and code field in the packet. ICMP error packets are classified into query packets and error packets. ICMP error packets sometimes require special processing. For example, in response to ICMP error packets, another ICMP error packet is never generated.

For the ping program we used today, we used:

  • The query packet whose type is 0 and code is 0 is displayed
  • Query packet whose type is 8 and code is 0 is displayed

2.2 Introduction to the Ping Program Protocol

The working principle of ping is simple. One network device sends a request and waits for another network device to reply, and records the sending time. After receiving the reply, you can calculate the packet transmission time. As long as a reply is received, the connection is normal. The elapsed time indicates the length of the path. The consistency of repeated request responses also indicates the reliability of connection quality. Thus, ping answers two basic questions: Is there a connection? What is the quality of the connection?

We call the ping program that sends the echo request the client and the host that is pinged the server. Most TCP/IP implementations support the ping server directly in the kernel, which is not a user process. ICMP Response packets and request packets are as follows:

The Unix system sets the identifier field in ICMP packets to the ID number of the sending process when the ping program is implemented. This allows the ping program to recognize the returned information even if multiple instances of the ping program are running on the same host. The sequence number starts at 0 and is incremented by 1 each time a new echo request is sent.

2.3 Ping Program Commands

The main options of the ping program:

  1. -c: allows you to specify the number of packets to be sent. For example, ping -c10 sends 10 packets and then stops.
  2. -f: indicates that the packet sending rate is the same as the processing rate of the receiving host. This parameter can be used for link pressure test or interface performance comparison.
  3. -l: the option is used for counting. Send the number of packets as soon as possible, and then recover. This command is used to test the ability to process flooding.
  4. -i: The option is used to specify the number of seconds to wait between two consecutive packets. This command is useful for separating packets or in scripts. Under normal circumstances, accidental ping packets have little impact on data flow. However, the impact of repeated packets or packet flooding is significant. Therefore, use this option with caution;
  5. -n: the -n option limits the output to digital form, which is useful if you have DNS problems;
  6. -v: displays more detailed output. -q and -q are seldom output.
  7. -s: specifies the size of the data to be sent. However, if the value is set to less than 8, there is no space left in the message for the timestamp. Setting the packet size can diagnose problems caused by Maximum Transmission Unit (MTU) Settings or segments. If this option is not used, ping defaults to 64 bytes.

2.4 Run the ping program on the Android

Android system provides the ping command line program, in the program can use popen to execute the system’s own ping program, the following is the code to execute the ping program:

int RunPingQuery(int _querycount, int interval/*S*/, int timeout/*S*/, const char* dest, unsigned int packetSize) { char cmd[256] = {0}; int index = snprintf(cmd, 256, "ping -c %d -i %d -w %d", _querycount, interval, timeout); if (index < 0 || index >= 256) { //sprintf return error return -1; } int tempLen = 0; if (packetSize > 0) { tempLen = snprintf((char*)&cmd[index], 256 - index, " -s %u %s", packetSize, dest); } else { tempLen = snprintf((char*)&cmd[index], 256 - index, " %s", dest); } if (tempLen < 0 || tempLen >= 256 - index) { //sprintf return error return -1; } FILE* pp = popen(cmd, "r"); if (! pp) { //popen error return -1; } std::string pingresult_; while (fgets(line, sizeof(line), pp) ! = NULL) { pingresult_.append(line, strlen(line)); } pclose(pp); if (pingresult_.empty()) { //m_strPingResult is empty return -1; } struct PingStatus pingStatusTemp; / / = {0}; notice: cannot initial with = {0},crash GetPingStatus(pingStatusTemp); if (0 == pingStatusTemp.avgrtt && 0 == pingStatusTemp.maxrtt) { //remote host is not available return -1; } return 0; } int GetPingStatus(struct PingStatus& _ping_status, std::string pingresult_) { if (pingresult_.empty()) return -1; _ping_status.res = pingresult_; std::vector<std::string> vecPingRes; str_split('\n', pingresult_, vecPingRes); std::vector<std::string>::iterator iter = vecPingRes.begin(); for (; iter ! = vecPingRes.end(); ++iter) { if (vecPingRes.begin() == iter) { // extract ip from the result string and assign to _ping_status.ip int index1 = iter->find_first_of("(", 0); if (index1 > 0) { int index2 = iter->find_first_of(")", 0); if (index2 > index1) { int size = index2 - index1 - 1; std::string ipTemp(iter->substr(index1 + 1, size)); strncpy(_ping_status.ip, ipTemp.c_str(), (size < 16 ? size : 15)); } } } // end if(vecPingRes.begin()==iter) int num = iter->find("packet loss", 0); if (num >= 0) { int loss_rate = 0; int i = 3; while (iter->at(num - i) ! = ' ') {loss_rate + = ((iter - > the at (num - I) - '0'), * (int) pow (10.0, (double) (I - 3))); i++; } _ping_status.loss_rate = (double)loss_rate / 100; } int num2 = iter->find("rtt min/avg/max", 0); if (num2 >= 0) { int find_begpos = 23; int findpos = iter->find_first_of('/', find_begpos); std::string sminRTT(*iter, find_begpos, findpos - find_begpos); find_begpos = findpos + 1; findpos = iter->find_first_of('/', find_begpos); std::string savgRTT(*iter, find_begpos, findpos - find_begpos); find_begpos = findpos + 1; findpos = iter->find_first_of('/', find_begpos); std::string smaxRTT(*iter, find_begpos, findpos - find_begpos); _ping_status.minrtt = atof(sminRTT.c_str()); _ping_status.avgrtt = atof(savgRTT.c_str()); _ping_status.maxrtt = atof(smaxRTT.c_str()); } } return 0; }Copy the code

2.5 The iOS Sends the ping Command

The iOS server sends ICMP packets by creating sockets. The main ideas are as follows:

  1. If the domain name is set, convert the DNS to an IP address.
  2. Create a socketn = socket(family, type, protocol), family is AF_INET, type is SOCK_DGRAM, protocol is IPPROTO_ICMP.
  3. Constructing ICMP packets:
struct icmp { u_char icmp_type; /* type of message, see below */ u_char icmp_code; /* type sub code */ u_short icmp_cksum; /* ones complement cksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ struct ih_idseq { n_short icd_id; n_short icd_seq; } ih_idseq; int ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { n_short ipm_void; n_short ipm_nextmtu; } ih_pmtu; struct ih_rtradv { u_char irt_num_addrs; u_char irt_wpa; u_int16_t irt_lifetime; } ih_rtradv; } icmp_hun; #define icmp_pptr icmp_hun.ih_pptr #define icmp_gwaddr icmp_hun.ih_gwaddr #define icmp_id icmp_hun.ih_idseq.icd_id #define icmp_seq icmp_hun.ih_idseq.icd_seq #define icmp_void icmp_hun.ih_void #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime union { struct id_ts { n_time its_otime; n_time its_rtime; n_time its_ttime; } id_ts; struct id_ip { struct ip idi_ip; /* options and then 64 bits of data */ } id_ip; struct icmp_ra_addr id_radv; u_int32_t id_mask; char id_data[1]; } icmp_dun; #define icmp_otime icmp_dun.id_ts.its_otime #define icmp_rtime icmp_dun.id_ts.its_rtime #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data }; void __preparePacket(char* _sendbuffer, int& _len) { char sendbuf[MAXBUFSIZE]; memset(sendbuf, 0, MAXBUFSIZE); struct icmp* icmp; icmp = (struct icmp*) sendbuf; icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_id = getpid() & 0xffff; /* ICMP ID field is 16 bits */ icmp->icmp_seq = htons(nsent_++); memset(&sendbuf[ICMP_MINLEN], 0xa5, DATALEN); /* fill with pattern */ struct timeval now; (void)gettimeofday(&now, NULL); now.tv_usec = htonl(now.tv_usec); now.tv_sec = htonl(now.tv_sec); bcopy((void*)&now, (void*)&sendbuf[ICMP_MINLEN], sizeof(now)); _len = ICMP_MINLEN + DATALEN; /* checksum ICMP header and data */ icmp->icmp_cksum = 0; icmp->icmp_cksum = in_cksum((u_short*) icmp, _len); memcpy(_sendbuffer, sendbuf, _len); }Copy the code
  1. Receiving ICMP packets:
int PingQuery::__recv() { char recvbuf[MAXBUFSIZE]; char controlbuf[MAXBUFSIZE]; memset(recvbuf, 0, MAXBUFSIZE); memset(controlbuf, 0, MAXBUFSIZE); struct msghdr msg = {0}; struct iovec iov = {0}; iov.iov_base = recvbuf; iov.iov_len = sizeof(recvbuf); msg.msg_name = &recvaddr_; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = controlbuf; msg.msg_namelen = sizeof(recvaddr_); msg.msg_controllen = sizeof(controlbuf); int n = (int)recvmsg(sockfd_, &msg, 0); if (n < 0) { return -1; } // Parse message structure return n; }Copy the code

2.6 Ping Network Delay Reference

If the ping external network is smaller than 50ms, the network latency is normal, even if good.

Generally speaking, the lower the delay PING value is, the higher the speed is. However, there is no necessary connection between network speed and network delay. The following is the reference data of ping network delay:

  • Ping network: 1 to 30ms: extremely fast speed, almost no detectable delay, playing any game speed is particularly smooth; ~
  • Ping network: 31 to 50ms: good speed, can play normal web browsing, no obvious delay; ~
  • Ping network: 51 to 100ms: normal speed, antagonistic games above a certain level can feel the delay, occasionally feel the pause;
  • Ping network: 100ms to 200ms: Poor speed, unable to play confrontation games normally, obvious lag phenomenon, occasional packet loss and disconnection phenomenon.

3. DNS resolution takes time

Before we create the socket, there is a domain name to IP resolution process. The domain name System (DNS) is a distributed database for TCP/IP applications, providing the translation between domain names and IP addresses and routing information about E-mail. On Unix hosts, this is accessed through two library functions gethostbyname and gethostbyaddr. The former receives the host name and returns the IP address, while the latter receives the IP address to find the host name.

We know that domain name resolution accesses domain name service, is the connection to domain name service based on UDP or TCP? The well-known port number used by DNS name servers is 53. Tcpdump shows that UDP is used in all examples.

When the name resolver issues a query request and the TC (truncation flag) bit in the response is set to 1, it means that the response is more than 512 bytes long and only the first 512 bytes are returned. In this case, the name resolver resends the original query request using TCP, which allows the response to be returned in excess of 512 bytes. TCP can divide a user’s data stream into several message segments, and it can use multiple message segments to transmit arbitrary length of user data.

To calculate the DNS resolution delay, we need to create sockets, send DNS packets, and obtain the calculation time of response. To create a socket, you need to know the DNS service address. How to obtain the DNS address?

A common method is to obtain the phone profile:

char buf1[PROP_VALUE_MAX];
char buf2[PROP_VALUE_MAX];
__system_property_get("net.dns1", buf1);
__system_property_get("net.dns2", buf2);
Copy the code

You can obtain DNS service addresses for some mobile phones of advanced versions as follows:

char buf3[1024]; __system_property_get("ro.config.dnscure_ipcfg", buf3); std::string dnsCureIPCfgStr(buf3); if (! dnsCureIPCfgStr.empty()) { const std::vector<std::string> &kVector = splitstr(dnsCureIPCfgStr, '|'); if (kVector.size() > 2) { const std::vector<std::string> &kVector2 = splitstr(dnsCureIPCfgStr, '; '); if (kVector2.size() > 2) { _dns_servers.push_back(kVector2[0]); / / the main DNS _dns_servers. Push_back (kVector2 [1]). / / for DNS return; }}}Copy the code

The method to get to the DNS list to comma-separated list address, outside the Intranet network by |.

Obtain from ConnectivityManager:

private static String[] getDnsFromConnectionManager(Context context) { LinkedList<String> dnsServers = new LinkedList<>(); if (Build.VERSION.SDK_INT >= 21 && context ! = null) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE); if (connectivityManager ! = null) { NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); if (activeNetworkInfo ! = null) { for (Network network : connectivityManager.getAllNetworks()) { NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); if (networkInfo ! = null && networkInfo.getType() == activeNetworkInfo.getType()) { LinkProperties lp = connectivityManager.getLinkProperties(network); for (InetAddress addr : lp.getDnsServers()) { dnsServers.add(addr.getHostAddress()); } } } } } } return dnsServers.isEmpty() ? new String[0] : dnsServers.toArray(new String[dnsServers.size()]); }Copy the code

The Intranet DNS address is obtained. To obtain the DNS service address on an Android server, consider the Android brand and system compatibility.

4. TCP connection time statistics

TCP Time Time of socket creation, connection, and sending and receiving messages:

  1. Create a socketsocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  2. Establish a connection:int connectRet = connect(fsocket, (sockaddr*)&_addr, sizeof(_addr));
  3. Send test instructions:send
  4. Receiving messages:recv

5. To summarize

This article discusses the mobile network quality statistics, including ping time, DNS time, and TCP connection time. The compatibility of DNS service address and TCP socket read and write times should be considered in mobile terminal, and the network quality evaluation method is briefly introduced.


Please feel free to discuss in the comments section. The nuggets will draw 100 nuggets in the comments section after the diggnation project. See the event article for details