Android Traceroute 功能實現

希爾瓦娜斯女神發表於2015-01-19

經常在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 下面除錯你的命令結果。分析結果集。

 

相關文章