1.pin的作用
Ping是潜水艇人员的专用术语,表示回应的声纳脉冲,在网络中Ping是一个十分好用的TCP/IP工具。它主要的功能是用来检测网络的连通情况和分析网络速度。
====================分隔符=====================
2.ping的命令
Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]
[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]
[-w deadline] [-W timeout] [hop1 …] destination
参数 详解
-a Audible ping.
-A 自适应ping,根据ping包往返时间确定ping的速度;
-b 允许ping一个广播地址;
-B 不允许ping改变包头的源地址;
-c count ping指定次数后停止ping;
-d 使用Socket的SO_DEBUG功能;
-F flow_label 为ping回显请求分配一个20位的“flow label”,如果未设置,内核会为ping随机分配;
-f 极限检测,快速连续ping一台主机,ping的速度达到100次每秒;
-i interval 设定间隔几秒发送一个ping包,默认一秒ping一次;
-I interface 指定网卡接口、或指定的本机地址送出数据包;
-l preload 设置在送出要求信息之前,先行发出的数据包;
-L 抑制组播报文回送,只适用于ping的目标为一个组播地址
-n 不要将ip地址转换成主机名;
-p pattern 指定填充ping数据包的十六进制内容,在诊断与数据有关的网络错误时这个选项就非常有用,如:“-p ff”;
-q 不显示任何传送封包的信息,只显示最后的结果
-Q tos 设置Qos(Quality of Service),它是ICMP数据报相关位;可以是十进制或十六进制数,详见rfc1349和rfc2474文档;
-R 记录ping的路由过程(IPv4 only);
注意:由于IP头的限制,最多只能记录9个路由,其他会被忽略;
-r 忽略正常的路由表,直接将数据包送到远端主机上,通常是查看本机的网络接口是否有问题;如果主机不直接连接的网络上,则返回一个错误。
-S sndbuf Set socket sndbuf. If not specified, it is selected to buffer not more than one packet.
-s packetsize 指定每次ping发送的数据字节数,默认为“56字节”+“28字节”的ICMP头,一共是84字节;
包头+内容不能大于65535,所以最大值为65507(linux:65507, windows:65500);
-t ttl 设置TTL(Time To Live)为指定的值。该字段指定IP包被路由器丢弃之前允许通过的最大网段数;
-T timestamp_option 设置IP timestamp选项,可以是下面的任何一个:
‘tsonly’ (only timestamps)
‘tsandaddr’ (timestamps and addresses)
‘tsprespec host1 [host2 [host3]]’ (timestamp prespecified hops).
-M hint 设置MTU(最大传输单元)分片策略。
可设置为:
‘do’:禁止分片,即使包被丢弃;
‘want’:当包过大时分片;
‘dont’:不设置分片标志(DF flag);
-m mark 设置mark;
-v 使ping处于verbose方式,它要ping命令除了打印ECHO-RESPONSE数据包之外,还打印其它所有返回的ICMP数据包;
-U Print full user-to-user latency (the old behaviour).
Normally ping prints network round trip time, which can be different f.e. due to DNS failures.
-W timeout 以毫秒为单位设置ping的超时时间;
-w deadline deadline;
====================分隔符=====================
3.ping的原理
ping命令本质也是属于应用层的一个应用,但是却走了TCP/IP一整套协议,分析清楚实现原理是很有必要的。应用层到网络层就不多说了,无外乎就是通过C库的调用。重点分析一下IP层以下的数据传输。
我们这里先假定两个主机的IP地址分别为:A->172.16.1.1和B->172.16.1.2,很显然他们是在同一个子网内的。
当我们在主机A执行ping 172.16.1.2时会发生什么呢?
主机A:
首先,Ping命令会构建一个固定格式的ICMP(网络控制报文协议)请求数据包(注意这里只是有格式的数据); —>ICMP报文:应用层到网络层
然后由ICMP协议将这个数据包连同地址(“172.16.1.1”的IP地址)一起交给IP层协议(和ICMP一样,实际上是一组后台运行的进程),IP层协议将以地址“172.16.1.2”作为目的地址,本机IP地址作为源地址,加上一些其他的控制信息,构建一个IP数据包(这个时候的IP数据包就不只是简单的数据了,包括地址,控制信息等),并在一个映射表中查找出IP地址172.16.1.2所对应的物理地址(也叫MAC地址,这是数据链路层协议构建数据链路层的传输单元——帧所必需的),一并交给数据链路层。—>IP报文:网络层到数据链路层
接着数据链路层构建一个数据帧,目的地址是IP层传过来的物理地址,源地址则是本机的物理地址,还要附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。—>数据链路帧:数据链路层到物理层
最后当然就是沿着传输线从主机A到主机B了。 —>数据bit:A的物理层到B的物理层
主机B:
首先主机B收到这个数据帧后,先检查它的目的地址,并和本机的物理地址对比,如符合,则接收;否则丢弃。—>数据bit:物理层到数据链路层
接收后检查该数据帧,将IP数据包从帧中提取出来(这里主要是剔除地址,即回到之前的数据链路帧),交给本机的IP层协议。—>数据链路帧:数据链路层到网络层
同样,IP层检查后,IP协议将有用的信息提取后交给ICMP协议。—>IP报文:网络层
最后ICMP协议处理后,马上构建一个ICMP应答包,发送给主机A,其过程和主机A发送ICMP请求包到主机B一模一样。—>ICPM报文:网络层到应用层
从Ping的工作过程,我们可以知道,主机A收到了主机B的一个应答包,说明两台主机之间的去、回通路均正常。也就是说,无论从主机A到主机B,还是从主机B到主机A,都是正常的。
====================分隔符=====================
4.ping失败的情况
4.1防火墙设置
一般作为服务器的主机都安装了防火墙软件(所谓“防火墙”是指一种将内部网和公众访问网(如Internet)分开的方法,它实际上是一种建立在现代通信网络技术和信息安全技术基础上的应用性安全技术,隔离技术。),默认情况下是不允许其他机器Ping本机的。一般的做法是将来自外部的ICMP请求报文滤掉,但它却对本机出去的ICMP请求报文,以及来自外部的ICMP应答报文不加任何限制。这样,从本机Ping其他机器时,如果网络正常,就没有问题。但如果从其他机器Ping这台机器,即使网络一切正常,也会出现“超时无应答”的错误。大部分的单方向Ping通现象源于此。
解决的办法:
根据你自己所用的不同类型的防火墙,调整相应的设置即可。
4.2错误设置IP地址
正常情况下,一台主机应该有一个网卡,一个IP地址,或多个网卡,多个IP地址(这些地址一定要处于不同的IP子网)。但对于在公共场所使用的电脑,特别是网吧,人多手杂,有时候其中一台电脑的“拨号网络适配器”(相当于一块软网卡)的TCP/IP设置中,会设置一个与网卡IP地址处于同一子网的IP地址,这样,在IP层协议看来,这台主机就有两个不同的接口处于同一网段内。当从这台主机Ping其他的机器时,会存在这样的问题:
(1)主机不知道将数据包发到哪个网络接口,因为有两个网络接口都连接在同一网段;
(2)主机不知道用哪个地址作为数据包的源地址。因此,从这台主机去Ping其他机器,IP层协议会无法处理,超时后,Ping 就会给出一个“超时无应答”的错误信息提示。但从其他主机Ping这台主机时,请求包从特定的网卡来,ICMP只须简单地将目的、源地址互换,并更改一些标志即可,ICMP应答包能顺利发出,其他主机也就能成功Ping通这台机器了。
解决办法:检查IP地址
====================分隔符=====================
5.如何用Ping命令来判断一条链路好坏?
1). 使用ipconfig /all观察本地网络设置是否正确,
2). Ping 127.0.0.1,127.0.0.1 回送地址Ping回送地址是为了检查本地的TCP/IP协议有没有设置好;
3). Ping本机IP地址,这样是为了检查本机的IP地址是否设置有误;
4). Ping本网网关或本网IP地址,这样的是为了检查硬件设备是否有问题,也可以检查本机与本地网络连接是否正常;(在非局域网中这一步骤可以忽略)。
====================分隔符=====================
6.ping常见返回状态
6.1成功返回
"bytes=32"表示ICMP报文中有32个字节的测试数据,"time=4ms"是往返时间。Sent发送多个秒包、Received 收到多个回应包、Lost 丢弃了多少个Minmum 最小值、MAXimun 最大值、Average 平均值。(更详细可以使用-n参数 “ping –n 100IP地址”ping 100次。查看 Sent Received Lost Minmum MAXimun Average 这些值的变化。)
6.2Request timed out
这是大家经常碰到的提示信息,很多文章中说这是对方机器置了过滤ICMP数据包,从上面工作过程来看,这是不完全正确的,至少有下几种情况。
(1)对方已关机,或者网络上根本没有这个地址:比如在上图中主机A中PING 192.168.0.7,或者主机B关机了,在主机A中PING 192.168.0.5 都会得到超时的信息。
(2)对方与自己不在同一网段内,通过路由也无法找到对方,但有时对方确实是存在的,当然不存在也是返回超时的信息。
(3)对方确实存在,但设置了ICMP数据包过滤(比如防火墙设置)。
(4)错误设置IP地址
怎样知道对方是存在,还是不存在呢,可以用带参数 -a 的Ping命令探测对方,如果能得到对方的NETBIOS名称,则说明对方是存在的,是有防火墙设置,如果得不到,多半是对方不存在或关机,或不在同一网段内。
6.3Destination host Unreachable
(1)对方与自己不在同一网段内,而自己又未设置默认的路由,比如上例中A机中不设定默认的路由,运行Ping 192.168.0.1.4就会出现“Destination host Unreachable”。
(2) 网线 出了故障
这里要说明一下“destination host unreachable”和 “time out”的区别,如果所经过的路由器的路由表中具有到达目标的路由,而目标因为其他原因不可到达,这时候会出现“time out”,如果路由表中连到达目标的路由都没有,那就会出现“destination host unreachable”。
6.4Bad IP address
这个信息表示您可能没有连接到DNS服务器,所以无法解析这个IP地址,也可能是IP地址不存在。特别是针对使用ping www.baidu.com(网址)的情况。
6.5Source quench received
这个信息比较特殊,它出现的机率很少。它表示对方或中途的服务器繁忙无法回应。
6.6Unknown host——不知名主机
这种出错信息的意思是,该远程主机的名字不能被域名服务器(DNS)转换成IP地址。故障原因可能是域名服务器有故障,或者其名字不正确,或者网络管理员的系统与远程主机之间的通信线路有故障。
6.7No answer——无响应
这种故障说明本地系统有一条通向中心主机的路由,但却接收不到它发给该中心主机的任何信息。故障原因可能是下列之一:中心主机没有工作;本地或中心主机网络配置不正确;本地或中心的路由器没有工作;通信线路有故障;中心主机存在路由选择问题。
6.8Ping 127.0.0.1:127.0.0.1是本地循环地址
如果本地址无法Ping通,则表明本地机TCP/IP协议不能正常工作。
6.9no rout to host:网卡工作不正常。
6.10transmit fai led ,error code:10043
网卡驱动不正常。
6.11unknown host name
DNS配置不正确。
====================分隔符=====================
7.源码分析
ping命令使用的是ICMP协议。ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。
/*************************************************************************
> File Name: ping.c
> Author: flyranchao
> Mail: flyranchao@allwinnertech.com
> Created Time: 2019年08月02日 星期五 20时21分42秒
> Func: ping命令是向目的主机发送ICMP报文,检验本地主机和远程的目的主机是否连接
************************************************************************/
/*ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并行校验*/
/***********主函数*********************************************
myping.c*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h> /* bzero */
#include <netdb.h>
#include <pthread.h>
//保存发送包的状态值
typedef struct pingm_pakcet{
struct timeval tv_begin; //发送时间
struct timeval tv_end; //接收到的时间
short seq; //序列号
int flag; //1,表示已经发送但是没有接收到回应,0,表示接收到回应
}pingm_pakcet;
static pingm_pakcet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char *data, int len);
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv,int length);
static int icmp_unpack(char *buf,int len);
static void *icmp_recv(void *argv);
static void icmp_sigint(int signo);
static void icmp_usage();
static pingm_pakcet pingpacket[128];
#define K 1024
#define BUFFERSIZE 72 //发送缓冲区的大小
static unsigned char send_buff[BUFFERSIZE];
static unsigned char recv_buff[2*K]; //防止接收溢出,设置大一些
static struct sockaddr_in dest; //目的地址
static int rawsock = 0; //发送和接收线程需要的socket描述符
static pid_t pid; //进程PID
static int alive = 0; //是否接收到退出信号
static short packet_send = 0; //已经发送的数据包数量
static short packet_recv = 0; //已经接收的数据包数量
static char dest_str[80]; //目的主机字符串
static struct timeval tv_begin, tv_end, tv_interval;
static void icmp_usage()
{
//ping加IP地址或者域名
printf("ping aaa.bbb.ccc.ddd\n");
}
/*终端信号处理函数SIGINT*/
static void icmp_sigint(int signo)
{
alive = 0;
gettimeofday(&tv_end,NULL);
tv_interval = icmp_tvsub(tv_end, tv_begin);
return;
}
/*统计数据结果函数******************************************
打印全部ICMP发送的接收统计结果*/
static void icmp_statistics(void)
{
long time = (tv_interval.tv_sec * 1000) + (tv_interval.tv_usec/1000);
printf("--- %s ping statistics ---\n", dest_str);
printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n",
packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);
}
/*************查找数组中的标识函数***********************
查找合适的包的位置
当seq为1时,表示查找空包
其他值表示查找seq对应的包*/
static pingm_pakcet *icmp_findpacket(int seq)
{
int i;
pingm_pakcet *found = NULL;
//查找包的位置
if(seq == -1){
for(i=0;i<128;i++){
if(pingpacket[i].flag == 0){
found = &pingpacket[i];
break;
}
}
}
else if(seq >= 0){
for(i =0 ;i< 128;i++){
if(pingpacket[i].seq == seq){
found = &pingpacket[i];
break;
}
}
}
return found;
}
/*************校验和函数*****************************
TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果,
CRC16校验和计算icmp_cksum
参数:
data:数据
len:数据长度
返回值:
计算结果,short类型
*/
static unsigned short icmp_cksum(unsigned char *data, int len)
{
int sum = 0; //计算结果
int odd = len & 0x01; //是否为奇数
/*将数据按照2字节为单位累加起来*/
while(len & 0xfffe){
sum += *(unsigned short*)data;
data += 2;
len -= 2;
}
/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/
if(odd){
unsigned short tmp = ((*data)<<8)&0xff00;
sum += tmp;
}
sum = (sum >> 16) + (sum & 0xffff); //高地位相加
sum += (sum >> 16); //将溢出位加入
return ~sum; //返回取反值
}
/**********进行ICMP头部校验********************/
//设置ICMP报头
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length)
{
unsigned char i = 0;
//设置报头
icmph->icmp_type = ICMP_ECHO; //ICMP回显请求
icmph->icmp_code = 0; //code的值为0
icmph->icmp_cksum = 0; //先将cksum的值填为0,便于以后的cksum计算
icmph->icmp_seq = seq; //本报的序列号
icmph->icmp_id = pid & 0xffff; //填写PID
for(i=0; i< length; i++)
icmph->icmp_data[i] = i; //计算校验和
icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
}
/*解压接收到的包,并打印信息*/
static int icmp_unpack(char *buf, int len)
{
int i,iphdrlen;
struct ip *ip = NULL;
struct icmp *icmp = NULL;
int rtt;
ip = (struct ip *)buf; //IP报头
iphdrlen = ip->ip_hl * 4; //IP头部长度
icmp = (struct icmp *)(buf+iphdrlen); //ICMP段的地址
len -= iphdrlen;
//判断长度是否为ICMP包
if(len < 8){
printf("ICMP packets\'s length is less than 8\n");
return -1;
}
//ICMP类型为ICMP_ECHOREPLY并且为本进程的PID
if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){
struct timeval tv_interval,tv_recv,tv_send;
//在发送表格中查找已经发送的包,按照seq
pingm_pakcet *packet = icmp_findpacket(icmp->icmp_seq);
if(packet == NULL)
return -1;
packet->flag = 0; //取消标志
tv_send = packet->tv_begin; //获取本包的发送时间
gettimeofday(&tv_recv,NULL); //读取此时间,计算时间差
tv_interval = icmp_tvsub(tv_recv,tv_send);
rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000;
/*打印结果包含
ICMP段的长度
源IP地址
包的序列号
TTL
时间差
*/
printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
packet_recv ++; //接收包数量加1
}
else {
return -1;
}
}
/************计算时间差time_sub************************
参数:
end:接收到时间
begin:开始发送的时间
返回值:
使用的时间
*/
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin)
{
struct timeval tv;
//计算差值
tv.tv_sec = end.tv_sec - begin.tv_sec;
tv.tv_usec = end.tv_usec - begin.tv_usec;
//如果接收的时间的usec值小于发送时的usec,从uesc域借位
if(tv.tv_usec < 0){
tv.tv_sec --;
tv.tv_usec += 1000000;
}
return tv;
}
//**********发送报文***************************
static void *icmp_send(void *argv)
{
//保存程序开始发送数据的时间
gettimeofday(&tv_begin, NULL);
while(alive){
int size = 0;
struct timeval tv;
gettimeofday(&tv, NULL); //当前包的发送时间
//在发送包状态数组中找到一个空闲位置
pingm_pakcet *packet = icmp_findpacket(-1);
if(packet){
packet->seq = packet_send;
packet->flag = 1;
gettimeofday(&packet->tv_begin,NULL);
}
icmp_pack((struct icmp *)send_buff,packet_send,&tv, 64);
//打包数据
size = sendto(rawsock, send_buff,64,0,(struct sockaddr *)&dest, sizeof(dest));
if(size < 0){
perror("sendto error");
continue;
}
packet_send ++;
//每隔1s发送一个ICMP回显请求包
sleep(1);
}
}
//***********接收ping目的主机的回复***********
static void *icmp_recv(void *argv)
{
//轮询等待时间
struct timeval tv;
tv.tv_usec = 200;
tv.tv_sec = 0;
fd_set readfd;
//当没有信号发出一直接收数据
while(alive){
int ret = 0;
FD_ZERO(&readfd);
FD_SET(rawsock,&readfd);
ret = select(rawsock+1,&readfd,NULL,NULL,&tv);
switch(ret)
{
case -1:
//错误发生
break;
case 0:
//超时
break;
default :
{
//收到一个包
int fromlen = 0;
struct sockaddr from;
//接收数据
int size = recv(rawsock,recv_buff,sizeof(recv_buff),0);
if(errno == EINTR){
perror("recvfrom error");
continue;
}
//解包
ret = icmp_unpack(recv_buff,size);
if(ret == 1){
continue;
}
}
break;
}
}
}
/**********设置ICMP发送报文的头部*********************************
回显请求的ICMP报文
*/
/*struct icmp
{
u_int8_t icmp_type; //消息类型
u_int8_t icmp_code; //消息类型的子码
u_int16_t icmp_cksum; //校验和
union
{
struct ih_idseq //显示数据报
{
u_int16_t icd_id; //数据报ID
u_int16_t icd_seq; //数据报的序号
}ih_idseq;
}icmp_hun;
#define icmp_id icmp_hun.ih_idseq.icd_id;
#define icmp_seq icmp_hun.ih_idseq.icd_seq;
union
{
u_int8_t id_data[1]; //数据
}icmp_dun;
#define icmp_data icmp_dun.id_data;
}; */
//主程序
int main(int argc, char const *argv[])
{
struct hostent *host = NULL;
struct protoent *protocol = NULL;
char protoname[] = "icmp";
unsigned long inaddr = 1;
int size = 128*K;
if(argc < 2) //参数是否数量正确
{
icmp_usage();
return -1;
}
//获取协议类型
protocol = getprotobyname(protoname);
if(protocol == NULL)
{
perror("getprotobyname()");
return -1;
}
//复制目的地址字符串
memcpy(dest_str, argv[1],strlen(argv[1])+1);
memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);
//socket初始化
rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);
if(rawsock < 0){
perror("socket");
return -1;
}
pid = getuid(); //为与其他线程区别,加入pid
//增大接收缓冲区,防止接收包被覆盖
setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
bzero(&dest, sizeof(dest));
//获取目的地址的IP地址
dest.sin_family = AF_INET;
//输入的目的地址为字符串IP地址
inaddr = inet_addr(argv[1]);
if(inaddr == INADDR_NONE){ //输入的是DNS地址
host = gethostbyname(argv[1]);
if(host == NULL){
perror("gethostbyname");
return -1;
}
//将地址复制到dest
memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
} //IP地址字符串
else {
memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr));
}
//打印提示
inaddr = dest.sin_addr.s_addr;
printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n",
dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24);
//截取信号SIGINT,将icmp_sigint挂接上
signal(SIGINT,icmp_sigint);
/*发送数据并接收回应
建立两个线程,一个用于发数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行
下一步,最后对结果进行统计并打印
*/
alive = 1; //初始化可运行
pthread_t send_id, recv_id; //建立两个线程,用于发送和接收
int err = 0;
err = pthread_create(&send_id, NULL, icmp_send, NULL); //发送
if(err < 0){
return -1;
}
err = pthread_create(&recv_id, NULL, icmp_recv, NULL); //接收
if(err < 0){
return -1;
}
//等待线程结束
pthread_join(send_id, NULL);
pthread_join(recv_id, NULL);
//清理并打印统计结果
close(rawsock);
icmp_statistics();
return 0;
}
编译:gcc -o myping ping.c -lpthread
执行:./myping www.baidu.com(可能需要root权限)
8.推荐链接
https://www.cnblogs.com/machangwei-8/p/10352808.html ping命令参数
https://www.jb51.net/network/546149.html ping命令图解
https://blog.csdn.net/hezeyujiang/article/details/79504038 ping常见问题
https://blog.csdn.net/zzucsliang/article/details/41407387 ping原理和源码分析
https://blog.csdn.net/qq_33724710/article/details/51576444 一步一步实现ping命令