JS虽然发送不了真正Ping的ICMP数据包,但Ping的本质仍然是请求/回复的时间差,我们可以试图去请求站点的某个不存在资源,根据返回错误的时间,我们就可以计算出延时的时间差了。在本例,我们尝试访问指定站点下的一个不存在的图片文件(文件名是个随机数),以致onerror事件发生,这个过程大致就是网络上的延时差。
当然,实际过程中会复杂的多。HTTP1.1协议第一次访问时需要一个TCP连接的过程,要经过3次握手之后才开始HTTP通信,因此第一次就要慢一些。如果是ping一个域名,那还要包括域名解析的时间;其次,在理论上,之后的Request和Response是一个TCP-AP包和TCP-ACK包。但如果访问的是一个不存在的资源,服务器有可能会返回一个HTTP重定向,这时客户端就要再请求一次,直到重定向的页面数据收到后,才触发onerror事件,这其中的时间就要长的多了。 如果服务器404的数据长度大于数据包的MTU值,IP协议还会分包传输,这样延时就会成倍的增加。
另外一个可能的情况就是,请求一个不存在的主机,理应超时才对。但有可能运营商的DNS返回一个广告网站的IP,例如互联星空,以至于ping的很流畅。
附:源码
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>HTTP Ping</title> <style> html { height: 100%; overflow: hidden; } body { background: #000; color: #C0C0C0; font: bold 14px 'Lucida Console'; height: 100%; margin: 0 0 0 5px; } #divContent { height: 90%; overflow: auto; } #txtTimeout { width: 40px; } button { margin-left: 10px; } </style> </head> <body> <div id="divInput"> <span>Host:</span> <input id="txtURL" type="text" /> <span>Timeout:</span> <input id="txtTimeout" type="text" value="2000" /> <input id="btnSwitch" type="button" value="Start" onclick="handleClick()" /> <hr/> </div> <div id="divContent"></div> <script> var iSeq = 0; var iSent, iReply; var iSum, iMax, iMin; var sURL, iTimeout; var bRunning = false; var tick, tid; function reply() { if(!bRunning || this.seq != iSeq) return; /* * 取消超时计时 */ clearTimeout(tid); ++iReply; var delay = new Date - tick; iSum += delay; if(delay > iMax) iMax = delay; if(delay < iMin) iMin = delay; println("Reply from " + sURL + " time" + ((delay<1)? ("<1") : ("="+delay)) + "ms"); tid = setTimeout(ping, Math.max(1000-delay, 1000)); } function ping() { tick = +new Date; ++iSent; tid = setTimeout(timeout, iTimeout); var oImg = new Image; oImg.onload = reply; oImg.onerror = reply; oImg.seq = ++iSeq; // 发送请求 oImg.src = sURL + "/" + tick + ".html"; } function timeout() { if(!bRunning) return; println("Request timed out."); ping(); } </script> <script> function $(v){return document.getElementById(v)} var oBtn = $("btnSwitch"); var oContent = $("divContent"); var txtURL = $("txtURL"); txtURL.value = location.host; function handleClick() { if(bRunning) { /* * 停止 */ oBtn.value = "Start"; bRunning = false; clearTimeout(tid); /* * 统计结果 */ var lost = iSent - iReply; println(" "); println("Ping statistics for " + sURL + ":"); println(" Packets: Sent = " + iSent + ", Received = " + iReply + ", Lost = " + lost + " (" + Math.round(lost / iSent * 100) + "% loss),"); if(iReply == 0) return; println("Approximate round trip times in milli-seconds:"); println(" Minimum = " + iMin + "ms, Maximum = " + iMax + "ms, Average = " + Math.round(iSum / iReply) + "ms"); } else { /* * 开始 */ sURL = txtURL.value; if(sURL.length == 0) return; if(sURL.substring(0,7).toLowerCase() != "http://") sURL = "http://" + sURL; iTimeout = parseInt($("txtTimeout").value, 10); if(isNaN(iTimeout)) iTimeout = 2000; if(iTimeout < 1000) iTimeout = 1000; oBtn.value = "Stop "; bRunning = true; iSent = 0; iReply = 0; iSum = 0; iMax = -1; iMin = 1e9; cls(); println("Pinging " + sURL + ":"); println(" "); ping(); } } function println(str) { var div = document.createElement("div"); if(div.innerText != null) div.innerText = str; else div.textContent = str; oContent.appendChild(div); oContent.scrollTop = oContent.scrollHeight; } function cls() { oContent.innerHTML = ""; } </script> </body> </html>