經常在windows下開發網路功能的人 經常會使用的命令就是tracert 。而實際上 在app開發中,我們也經常要碰到類似的情況。比如你的app
出現了問題,你總不能讓使用者想辦法 去tracert吧。你肯定要知道你的app 是在網路中的哪一個部分出了問題。我舉個最簡單的例子。國內有很多做外包的
公司 在開發過程中 需要呼叫 facebook 等公司提供的sdk 或者介面。當然了 我們 在天朝嗎 所以我們在做類似功能的時候 一般要使用vpn來訪問。
但是很多vpn的情況 也是很不穩定的,在開發過程中 能否迅速的找到網路不通的原因 是很重要的。尤其是你給欠發達地區 的客戶在開發app的時候
一旦網路連線不同,你怎麼證明你的app 是執行正常的 ,是他們的網路基礎不正常?
那我們今天就來看一下如果在android app下面實現這個tracert功能。
首先我們想到的肯定是 實現一下tracert這個命令的java版,但實際上 我們在網路上找不到類似的資源,自己去實現包括icmp 報文之類的又比較麻煩(日後我會自己實現一份)
所以我們想到 android 是基於linux的 為何不直接呼叫這個命令呢?我們取得tracert的結果不就行了嗎,但實際上很多linux 版本是沒有 tracert的這個命令的。
當然了 很多人會說 linux下是叫 Traceroute 這個命令的,但是很遺憾的是多數android 手機內建命令都不包含這個。。。。
經過一番思索 我決定 用ping 命令的引數 來實現 Traceroute 這個功能。 其實主要是 -t 和 -c 這2個引數了。另外就是對ttl 這個值有一定的理解 即可實現類似於
Traceroute 的功能。
程式碼如下:
1 package com.example.traceroute; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.net.InetAddress; 7 import java.net.UnknownHostException; 8 import java.util.ArrayList; 9 import java.util.List; 10 11 import android.app.Activity; 12 import android.os.AsyncTask; 13 import android.os.Bundle; 14 import android.text.TextUtils; 15 import android.util.Log; 16 import android.view.View; 17 import android.widget.Button; 18 import android.widget.EditText; 19 20 public class MainActivity extends Activity { 21 22 // 輸入網址框 23 private EditText et; 24 25 // 開始traceroute的button 26 private Button searchButton; 27 28 // 最大的ttl跳轉 可以自己設定 29 private final int MAX_TTL = 30; 30 31 // 都是一些字串 用於parse 用的 32 private static final String PING = "PING"; 33 private static final String FROM_PING = "From"; 34 private static final String SMALL_FROM_PING = "from"; 35 private static final String PARENTHESE_OPEN_PING = "("; 36 private static final String PARENTHESE_CLOSE_PING = ")"; 37 private static final String TIME_PING = "time="; 38 private static final String EXCEED_PING = "exceed"; 39 private static final String UNREACHABLE_PING = "100%"; 40 41 // 初始化預設ttl 為1 42 private int ttl = 1; 43 private String ipToPing; 44 // ping耗時 45 private float elapsedTime; 46 47 // 存放結果集的tarces 48 private List<TracerouteContainer> traces = new ArrayList<TracerouteContainer>();; 49 50 @Override 51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 setContentView(R.layout.activity_main); 54 et = (EditText) this.findViewById(R.id.input); 55 searchButton = (Button) this.findViewById(R.id.search); 56 searchButton.setOnClickListener(new View.OnClickListener() { 57 58 @Override 59 public void onClick(View v) { 60 // TODO Auto-generated method stub 61 new ExecuteTracerouteAsyncTask(MAX_TTL, et.getText().toString()) 62 .execute(); 63 } 64 }); 65 } 66 67 private void showResultInLog() { 68 for (TracerouteContainer container : traces) { 69 Log.v("ccc", container.toString()); 70 } 71 } 72 73 /** 74 * 這個任務就是來更新我們的後臺log 日誌 把所得到的traceroute資訊列印出來。 75 * 76 */ 77 private class ExecuteTracerouteAsyncTask extends 78 AsyncTask<Void, Void, String> { 79 80 private int maxTtl; 81 82 private String url; 83 84 public ExecuteTracerouteAsyncTask(int maxTtl, String url) { 85 this.maxTtl = maxTtl; 86 this.url = url; 87 } 88 89 /** 90 * 後臺所做的工作 本質就是呼叫 ping命令 來完成類似traceroute的功能 91 */ 92 @Override 93 protected String doInBackground(Void... params) { 94 String res = ""; 95 try { 96 res = launchPing(url); 97 } catch (IOException e1) { 98 // TODO Auto-generated catch block 99 e1.printStackTrace(); 100 } 101 TracerouteContainer trace; 102 103 if (res.contains(UNREACHABLE_PING) && !res.contains(EXCEED_PING)) { 104 trace = new TracerouteContainer("", parseIpFromPing(res), 105 elapsedTime); 106 } else { 107 trace = new TracerouteContainer("", parseIpFromPing(res), 108 ttl == maxTtl ? Float 109 .parseFloat(parseTimeFromPing(res)) 110 : elapsedTime); 111 } 112 113 InetAddress inetAddr; 114 try { 115 inetAddr = InetAddress.getByName(trace.getIp()); 116 String hostname = inetAddr.getHostName(); 117 trace.setHostname(hostname); 118 } catch (UnknownHostException e) { 119 e.printStackTrace(); 120 } 121 traces.add(trace); 122 return res; 123 } 124 125 private String launchPing(String url) throws IOException { 126 Process p; 127 String command = ""; 128 129 // 這個實際上就是我們的命令第一封裝 注意ttl的值的變化 第一次呼叫的時候 ttl的值為1 130 String format = "ping -c 1 -t %d "; 131 command = String.format(format, ttl); 132 133 long startTime = System.nanoTime(); 134 // 實際呼叫命令時 後面要跟上url地址 135 p = Runtime.getRuntime().exec(command + url); 136 BufferedReader stdInput = new BufferedReader(new InputStreamReader( 137 p.getInputStream())); 138 139 String s; 140 String res = ""; 141 while ((s = stdInput.readLine()) != null) { 142 res += s + "\n"; 143 // 這個地方這麼做的原因是 有的手機 返回的from 有的手機返回的是From所以要 144 // 這麼去判定 請求結束的事件 算一下 延時 145 if (s.contains(FROM_PING) || s.contains(SMALL_FROM_PING)) { 146 elapsedTime = (System.nanoTime() - startTime) / 1000000.0f; 147 } 148 } 149 150 // 呼叫結束的時候 銷燬這個資源 151 p.destroy(); 152 153 if (res.equals("")) { 154 throw new IllegalArgumentException(); 155 } 156 // 第一次呼叫ping命令的時候 記得把取得的最終的ip地址 賦給外面的ipToPing 157 // 後面要依據這個ipToPing的值來判斷是否到達ip資料包的 終點 158 if (ttl == 1) { 159 ipToPing = parseIpToPingFromPing(res); 160 } 161 return res; 162 } 163 164 @Override 165 protected void onPostExecute(String result) { 166 // 如果為空的話就截止吧 過程完畢 167 if (TextUtils.isEmpty(result)) { 168 return; 169 } 170 171 // 如果這一跳的ip地址與最終的地址 一致的話 就說明 ping到了終點 172 if (traces.get(traces.size() - 1).getIp().equals(ipToPing)) { 173 if (ttl < maxTtl) { 174 ttl = maxTtl; 175 traces.remove(traces.size() - 1); 176 new ExecuteTracerouteAsyncTask(maxTtl, url).execute(); 177 } else { 178 // 如果ttl ==maxTtl的話 當然就結束了 我們就要列印出最終的結果 179 showResultInLog(); 180 } 181 } else { 182 // 如果比較的ip 不相等 哪就說明還沒有ping到最後一跳。我們就需要繼續ping 183 // 繼續ping的時候 記得ttl的值要加1 184 if (ttl < maxTtl) { 185 ttl++; 186 new ExecuteTracerouteAsyncTask(maxTtl, url).execute(); 187 } 188 } 189 super.onPostExecute(result); 190 } 191 192 } 193 194 /** 195 * 從結果集中解析出ip 196 * 197 * @param ping 198 * @return 199 */ 200 private String parseIpFromPing(String ping) { 201 String ip = ""; 202 if (ping.contains(FROM_PING)) { 203 int index = ping.indexOf(FROM_PING); 204 205 ip = ping.substring(index + 5); 206 if (ip.contains(PARENTHESE_OPEN_PING)) { 207 int indexOpen = ip.indexOf(PARENTHESE_OPEN_PING); 208 int indexClose = ip.indexOf(PARENTHESE_CLOSE_PING); 209 210 ip = ip.substring(indexOpen + 1, indexClose); 211 } else { 212 ip = ip.substring(0, ip.indexOf("\n")); 213 if (ip.contains(":")) { 214 index = ip.indexOf(":"); 215 } else { 216 index = ip.indexOf(" "); 217 } 218 219 ip = ip.substring(0, index); 220 } 221 } else { 222 int indexOpen = ping.indexOf(PARENTHESE_OPEN_PING); 223 int indexClose = ping.indexOf(PARENTHESE_CLOSE_PING); 224 225 ip = ping.substring(indexOpen + 1, indexClose); 226 } 227 228 return ip; 229 } 230 231 /** 232 * 從結果集中解析出ip 233 * 234 * @param ping 235 * @return 236 */ 237 private String parseIpToPingFromPing(String ping) { 238 String ip = ""; 239 if (ping.contains(PING)) { 240 int indexOpen = ping.indexOf(PARENTHESE_OPEN_PING); 241 int indexClose = ping.indexOf(PARENTHESE_CLOSE_PING); 242 243 ip = ping.substring(indexOpen + 1, indexClose); 244 } 245 246 return ip; 247 } 248 249 /** 250 * 從結果集中解析出time 251 * 252 * @param ping 253 * @return 254 */ 255 private String parseTimeFromPing(String ping) { 256 String time = ""; 257 if (ping.contains(TIME_PING)) { 258 int index = ping.indexOf(TIME_PING); 259 260 time = ping.substring(index + 5); 261 index = time.indexOf(" "); 262 time = time.substring(0, index); 263 } 264 265 return time; 266 } 267 268 }
其實程式碼本身並沒有多複雜 主要是要對linux有一定了解 另外要會在adb shell 下面除錯你的命令結果。分析結果集。