在做Android开发时,有时我们需要知道设备的网络好不好,光看手机上的信号格数是不准确的,比如在广州南站,人那么多,如果带宽不够的话,虽然你看着信号是满格的,但是网速也会很慢,有些地方,人少,信号也满格,但是网速也慢,所以不能光看信号强度,还是得通过ping命令来看网速比较可靠。
在Android的实际开发中,我们公司开发了音视频通讯App,安装在了客户的设备上,客户说,哎,怎么看不到视频啊,你这App不行啊,每次遇到这种问题我们就会说,是你的网络不行啊。啊哈,很搞笑,一有问题我们就会说是客户的网络不好导致的,但是每次你这么说的话也不太好啊,你要拿出证据来啊,客户说他信号明明是满格的呀!所以解决方案就是在App上面增加ping的功能,这样看网络好不好就比较有理有据了。当然,我们也可以远程ping,就是通过网络给客户的app发命令,app收到命令后就开始ping,ping完之后把结果通过网络再传到我们这边的app上,这样我们就可以远程查看客户的网络情况了。
ping的含义,ping (Packet Internet Groper)是一种因特网包探索器,用于测试网络连接量的程序 。Ping是工作在 TCP/IP网络体系结构中应用层的一个服务命令, 主要是向特定的目的主机发送 ICMP(Internet Control Message Protocol 因特网报文控制协议)Echo 请求报文,测试目的站是否可达及了解其有关状态。
ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。
Windows中的ping命令Windows中的ping命令可以通过 -l 设置发送数据包的大小,通过 -w 可以设置超时时间,示例如下:
Pinging 192.168.124.88 with 128 bytes of data: 这说明正在ping 192.168.124.88,数据包大小为128 bytes
Request timed out. 这说明ping超时了都没有收到192.168.124.88主机的回应。
Ping statistics for 192.168.124.88: 说明这是ping 192.168.124.88的结果分析。
Packets: Sent = 4, Received = 0, Lost = 4 (100% loss),
- Sent = 4,说明总共发送了4个数据包
- Received = 0,说明所有发出去的数据包中收到回应的个数为0个
- Lost = 4,说明发送的数据包中有4个包丢失了(即没有收到回应)
- 100% loss 说明所有发送的数据包中100%的数据包都丢失了
正常能ping通的效果图如下:
Pinging baidu.com[220.181.38.148] with 128 bytes of data: 这说明正在ping baidu.com[220.181.38.148] ,数据包大小为128 bytes
Reply from 220.181.38.148: bytes=128 time=37ms TTL=50
- Reply from 220.181.38.148: 这说明发送到220.181.38.148的数据包收到回应了
- bytes=128,说明发送的数据包大小为128 bytes
- time=37ms,说明数据包从发送出去到接收到回应,花了37毫秒时间。网络好不好就看这个时间了,像37毫秒就是网络非常好的了,在公司测试发现100毫秒上下也是OK的,数字越小越好,具体数字多大就会卡呢,自己试罢,反正如果你发现卡的时候,就看看这个数字是多少,慢慢的你就知道什么数字是好的,什么数字是差的了。
- TTL=50,TTL(Time To Live,生存周期):每经过一次路由该值自减1,直至减到0时该IP包会被丢弃。通过这样的设置可以在路由遇到死循环时,避免IP包在环内不停转发,但不能达到目的地。
Approximate round trip times in milli-seconds: 往返行程的估计时间
Minimum = 37ms, Maximum = 38ms, Average = 37ms
- 最短 = 37ms,即使用时间最短的那个数据包发送接收的使用时间
- 最长 = 38ms,即使用时间最长的那个数据包发送接收的使用时间
- 平均 = 37ms,即所有的数据包平均的发送和接收的使用时间
因为默认只ping四次,所以如果需要一直ping的话可以通过加 -t 参数,示例如下:
关于TTL,一般用默认值就行,为了方便理解,我们修改一下这个参数,使用 -i 可以修改,比如改成5次,然后ping baidu,看经过5次路由能否达到百度,如下:
在命令行中进入shell:
adb shell执行效果如下:
这时,我们就可以输入ping命令执行了,如下:
在我的Android项目需求中,不能一直ping,只需要ping一段时间即可,通过 -c 命令可以指定ping的次数,比如设置成只ping4次,如下:
PING baidu.com (220.181.38.148) 56(84) bytes of data. 这里的56(84),56指的是数据包的大小为56bytes,84是什么我就不知道了,似乎括号里的这个数值和不带括号的总是相差28。下面一行我们看到有64 bytes,和56 bytes相差8。
icmp_seq=1 ping序列,从1开始,干嘛用的?我也不知道,在ping的时候你可以通过这个值了解到这是发送的第几个数据包了。
4 packets transmitted, 4 received, 0% packet loss, time 3011ms
- 4 packets transmitted,说明传输了4个包
- time 3011ms 这是个什么时间?我也不知道,懒的去查了,开始我以为是整个ping过程所花的总时间,后来打开计时器看发现并不是。
rtt min/avg/max/mdev = 40.851/53.571/58.797/7.373 ms,rtt是舍意思?我也不知道,懒得查了,后面的,min是最短,avg是平均,max是最长,mdev?,我也不知道,懒得查。这里的最短、平均、最长和Widnows的是一样的含义,不多解释了。
在网络不好时,会发现ping命令一直没有输出内容,如下:
那在做Android开发时,如果网络不好ping命令一直不返回也不行啊,怎么结束ping操作啊?,我发现把ping的那个线程中断也不管用。
只输入ping就按回车,可以看到ping命令的所有参数,如下:
ping命令的参数很多,但是真正在用的时候需要的参数也就一两个,所以掌握这一两个就够了。Windows中的ping用-l和-t参数就够了,Linux中的ping用-s和-w就够了,超时时间一般是不用设置的,用默认的就好了。
Windows ping简单使用:ping -l 128 -t baidu.com
- l 设置数据包大小为128 bytes
- t 设置一直不停地ping
- 示例如下:
Linux ping简单使用:ping -s 120 -w 20 baidu.com
- s 设置数据包大小为120 bytes,实际发送数据包时是128bytes,据说是会包含一些头信息什么的需要额外的8bytes,据我的实验,在Android手机中,默认是1秒ping一次,包含超时时间在里面,比如ping 20秒,不管网络好与不好,20秒后,看统计信息会显示发送的刚好是20个包。
- w设置总的ping时间为20秒
- 示例如下:
查看ping参数说明
Windows直接输入ping即可,如下:
Linux也是直接输入ping,如下:
重点:Android使用此函数来执行ping命令:Runtime.getRuntime().exec(“ping -s 56 -w 30 192.168.1.8”),这个函数执行之后会返回一个Process进程对象,通过读取这个进程的两个输入流即可获取到ping的结果,需要注意的是,系统是有可能交替往这两个流里面写入数据的,所以我们需要开两个线程同时读取这两个流(百度的文章里全是一个线程进行读取的),这里使用到了线程的合并功能(join()函数),通过join()函数可以实现让两个线程先执行完,所以这个示例中完美的展示了join()函数的使用,大家可以深刻体会到该函数的作用是怎样的,准确的说,join()函数并不是线程合并函数,而是一个等待函数,比如在A线程里调用了B线程的join()函数,则A线程就等着不动了,等到B线程执行结束了A线程才继续接着往下执行。
先上一个效果图:
这里设置ping的ip为百度的ip,ping 5秒钟,数据包大小为64字节,点击Start Ping按钮,效果如下:
2、界面布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="" android:orientation="vertical" android:paddingTop="16dp" android:paddingBottom="16dp" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16dp" android:text="Ping IP: " tools:ignore="SpUsage" /> <EditText android:id="@+id/etIp" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp" android:inputType="phone" android:text="39.156.69.79" tools:ignore="Autofill,SpUsage" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16dp" android:text="Ping多久(单位为秒): " tools:ignore="SpUsage" /> <EditText android:id="@+id/etTime" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp" android:inputType="number" android:text="5" tools:ignore="Autofill,SpUsage" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16dp" android:text="数据包大小(单位为字节): " tools:ignore="SpUsage" /> <EditText android:id="@+id/etSize" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16dp" android:inputType="number" android:text="64" tools:ignore="Autofill,SpUsage" /> </LinearLayout> <Button android:gravity="center" android:id="@+id/btnStartPing" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Ping" android:textAllCaps="false" android:textColor="@android:color/white" android:textSize="16dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" tools:ignore="SpUsage" /> </LinearLayout>3、代码:
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var mAdapter: MyAdapter private val lines = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) mAdapter = MyAdapter() binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.adapter = mAdapter binding.btnStartPing.setOnClickListener { thread { ping() } } } private fun ping() { val ip = binding.etIp.text.trim().toString() val sizeStr = binding.etSize.text.trim().toString() val time = binding.etTime.text.trim().toString() if (ip.isBlank()) { runOnUiThread { Toast.makeText(this, "请输入IP", Toast.LENGTH_SHORT).show() } return } if (sizeStr.isBlank()) { runOnUiThread { Toast.makeText(this, "请输入数据包大小", Toast.LENGTH_SHORT).show() } return } if (time.isBlank()) { runOnUiThread { Toast.makeText(this, "请输入要ping多久", Toast.LENGTH_SHORT).show() } return } if (!isValidIpAddress(ip)) { runOnUiThread { AlertDialog.Builder(this) .setTitle("提示") .setMessage("您输入的IP地址格式有误,请修正!") .show() } return } runOnUiThread { binding.btnStartPing.isEnabled = false lines.clear() addData("Ping开始") } val size = sizeStr.toInt() - 8 val command = "ping -s $size -w $time $ip" // 注:正常ping数据和错误ping数据可能会交替输出,所以需要开两个线程同时读取 val process = Runtime.getRuntime().exec(command) val inputStreamThread = readData(process.inputStream) // 读取正常ping数据 val errorStreamThread = readData(process.errorStream) // 读取错误ping数据 // 等待两个读取线程结束 inputStreamThread.join() errorStreamThread.join() runOnUiThread { addData("Ping结束") binding.btnStartPing.isEnabled = true } } private fun readData(inputStream: InputStream?) = thread { try { BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { val lineTemp = line!! runOnUiThread { addData(lineTemp) } // 这里切换到了UI线程,子线程继续执行时可以已经把line对象又赋值为null了,所以使用了lineTemp来预防值被重新赋值 } } } catch (e: Exception) { runOnUiThread { addData("出现异常:${e.javaClass.simpleName}: ${e.message}") } } } private fun addData(data: String) { lines.add(data) refreshListView() } private fun refreshListView() { mAdapter.notifyDataSetChanged() binding.recyclerView.scrollToPosition(lines.size - 1) } /** * 验证给定的ip地址是否有效 * @param ip */ private fun isValidIpAddress(ip: String?): Boolean { if (ip.isNullOrBlank()) return false val regex = "(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})(\\.(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})){3}" val pattern = Pattern.compile(regex) val matcher = pattern.matcher(ip) return matcher.matches() } internal inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var textView: TextView = itemView.findViewById(android.R.id.text1) as TextView } internal inner class MyAdapter : RecyclerView.Adapter<MyViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { return MyViewHolder(View.inflate(parent.context, android.R.layout.simple_list_item_1,null)) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.textView.text = lines[position] } override fun getItemCount(): Int { return lines.size } } }也可从码云中下载完整代码:https://gitee.com/daizhufei/android-ping