你知道什么是 Ping 吗?

 欢迎到我的博客浏览 胤凯 (oyto.github.io)

这次我们来看一下什么是 Ping 操作,以及它有什么用处,并且我们来动手实现一个简易版的 Ping 工具。

Ping 是什么?

​    ping 是一个计算机网络工具,通常用于测试网络连接的可达性和测量往返时间。在大多数操作系统中,ping 命令是一个内置的命令行工具,可以通过命令行终端使用。例如,在 Windows 操作系统中,你可以在命令提示符中运行 ping 命令,而在类 Unix 操作系统(如 Linux 和 macOS)中,你可以在终端中使用 ping 命令。通常,命令的语法是 `ping 目标主机或 IP`,然后命令将输出与目标主机的通信状态和 RTT 相关的信息。

Ping 有什么用处?

​    `Ping` 工具主要有以下几个主要用途:

1. 测试主机的可达性:`ping` 命令用于检查另一个主机是否可以在网络上访问。它向目标主机发送一个小的数据包(通常是 ICMP Echo Request),如果目标主机正常工作,它将响应一个回复数据包(通常是 ICMP Echo Reply)。如果没有响应,那么目标主机可能无法访问或处于离线状态。
2. 测量往返时间(RTT):`ping` 命令通常会显示每次请求和响应之间的时间差,这被称为往返时间(RTT)。这个值表示了数据从发送端到接收端的往返延迟,通常以毫秒为单位。测量 RTT 对于评估网络性能和延迟非常有用。
3. 网络故障排除:`ping` 是网络故障排除的有用工具之一。通过检查 `ping` 的输出,网络管理员可以确定网络连接是否正常,以及延迟是否在可接受范围内。如果 `ping` 失败,管理员可以进一步调查网络故障的原因。
4. 监测网络稳定性:`ping` 命令还可以用于监测网络的稳定性。通过连续地向目标主机发送 `ping` 请求,可以了解网络连接的质量和稳定性。如果出现不稳定性,管理员可以及时采取措施。

动手实现一个 Ping 工具

​    首先,我们要了解一下 Ping 操作的工作原理:向网络上的另一个主机系统发送 ICMP 报文,如果指定系统得到了报文,它将把回复报文传回给发送者。

​    先来看看 ICMP 报文长什么样:

​    ICMP 报文由 ICMP 报文头 和 数据包组成,其报文头包含 Type、Code、Checksum、ID、SequenceNum 字段。因此,我们需要先在本地主机上定义 ICMP 请求报文结构体:

type ICMP struct {Type        uint8  // 类型Code        uint8  // 代码CheckSum    uint16 // 校验和ID          uint16 // IDSequenceNum uint16 // 序号
}

​    上面只是 ICMP 的报文头,我们在后面还需要为这个报文构建请求数据。需要注意的是,定义的顺序不能乱,因为我们发送数据包是按字节发送的,所以获取对应的字段的时候,也是按照对应字段的位置去获取的,如果顺序乱了,获取到的数据就会出错。

​    在构建数据之前,我们先设置好命令行参数,以获取对应参数和目标 IP,同时需要定义全局变量,将命令行参数绑定到对应的变量中,方便使用:

var (helpFlag booltimeout  int64 // 耗时size     int   // 大小count    int   // 请求次数
)func GetCommandArgs() {flag.Int64Var(&timeout, "w", 1000, "请求超时时间")flag.IntVar(&size, "l", 32, "发送字节数")flag.IntVar(&count, "n", 4, "请求次数")flag.BoolVar(&helpFlag, "h", false, "显示帮助信息")flag.Parse()
}

​    在 `main` 函数中,启用命令行参数设置:

func main() {GetCommandArgs()
}

​    在发送报文前,我们需要先建立连接,此时需要先获取目标 IP,这个由命令行参数中获取:

// 获取目标 IP
desIP := os.Args[len(os.Args)-1]
// 构建连接
conn, err := net.DialTimeout("ip:icmp", desIP, time.Duration(timeout)*time.Millisecond)
if err != nil {log.Println(err.Error())return
}
defer conn.Close()
// 远程地址
remoteaddr := conn.RemoteAddr()

​    连接建立后,我们需要根据参数中的发送次数 `count` 去发送对应次数的报文,因此需要用 `for` 去做:

for i := 0; i < count; i ++ {...
}

​    通过百度百科可以查到,我们要使用的是 Ping 请求,即回显请求,其对应的 Type 和 Code 如下:

​    同样,我们在全局变量中添加对应的值:

var (typ      uint8 = 8code     uint8 = 0
)

​    做好前面的准备工作,我们就可以开始构建我们的 ICMP 请求报文了。我们这里以发送的第几次作为 ID 和序列号:

icmp := &ICMP{Type:        typ,Code:        code,CheckSum:    uint16(0),ID:          uint16(i),SequenceNum: uint16(i),}

​    由于 ICMP 是使用二进制进行传输的,所以我们需要将信息用二进制表示:

var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, icmp)

​    然后根据发送数据的大小 `size` 构建数据并写在 ICMP 报文后面:

data := make([]byte, size)
buffer.Write(data)
data = buffer.Bytes()

​    现在,就只差一个校验和字段了,计算 ICMP(Internet Control Message Protocol)报文的校验和字段遵循以下步骤:

1. 将报文分为 16 位的字(两个字节)。
2. 对所有字进行按位求和(二进制求和),包括数据部分和报文头。如果有剩余字节(奇数个字节),将其附加到最后一个字节。
3. 将溢出的进位位(如果有)加回到结果中。
4. 取结果的二进制反码(按位取反)

func checkSum(data []byte) uint16 {// 第一步:两两拼接并求和length := len(data)index := 0var sum uint32for length > 1 {// 拼接且求和sum += uint32(data[index])<<8 + uint32(data[index+1])length -= 2index += 2}// 奇数情况,还剩下一个,直接求和过去if length == 1 {sum += uint32(data[index])}// 第二部:高 16 位,低 16 位 相加,直至高 16 位为 0hi := sum >> 16for hi != 0 {sum = hi + uint32(uint16(sum))hi = sum >> 16}// 返回 sum 值 取反return uint16(^sum)
}

​    接着再将算出来的校验和放到报文头对应的位置中去,这里需要计算一下位置。假设我们有以下 ICMP 报文:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      Type       |      Code       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Checksum (2 bytes)       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier (2 bytes)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Sequence Number (2 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Data (variable length) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

​    校验和属于报文的第3、4个字节,即 data[2] 和 data[3]。

data[2] = byte(checkSum >> 8)
data[3] = byte(checkSum)

​    最后再设置一下超时时间,就可以将数据 `data` 写入连接中了:

// 设置超时时间
conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))// 将 data 写入连接中,
n, err := conn.Write(data)
if err != nil {log.Println(err)continue
}

​    发送完成后,再构建缓冲接收响应包,

buf := make([]byte, 1024)
n, err = conn.Read(buf)
//fmt.Println(data)
if err != nil {log.Println(err)continue
}

​    然后我们就可以从响应包中获取我们需要的数据,比如 IP 地址、TTL等:

​    根据抓到的 ICMP 响应包,可以知道 IP 头共 20 个字节,源 IP 和 目标 IP 在我们接收的数据包的倒数 8 个字节里,所以我们可以推算出我们访问的 IP 地址,就可以构建我们的打印信息了:

fmt.Printf("来自 %d.%d.%d.%d 的回复:字节=%d 时间=%d TTL=%d\n", 
buf[12], buf[13], buf[14], buf[15], n-28, t, buf[8])

​    至此,我们 Ping 工具的核心功能就实现了,还有一些统计信息,就不做具体的讲解了,感兴趣的可以从代码中看具体的实现。

完整代码如下:

package mainimport ("bytes""encoding/binary""flag""fmt""log""math""net""os""time"
)// tcp 报文前20个是报文头,后面的才是 ICMP 的内容。
// ICMP:组建 ICMP 首部(8 字节) + 我们要传输的内容
// ICMP 首部:type、code、校验和、ID、序号,1 1 2 2 2
// 回显应答:type = 0,code = 0
// 回显请求:type = 8, code = 0var (helpFlag booltimeout  int64 // 耗时size     int   // 大小count    int   // 请求次数typ      uint8 = 8code     uint8 = 0SendCnt  int                   // 发送次数RecCnt   int                   // 接收次数MaxTime  int64 = math.MinInt64 // 最大耗时MinTime  int64 = math.MaxInt64 // 最短耗时SumTime  int64                 // 总计耗时
)// ICMP 序号不能乱
type ICMP struct {Type        uint8  // 类型Code        uint8  // 代码CheckSum    uint16 // 校验和ID          uint16 // IDSequenceNum uint16 // 序号
}func main() {fmt.Println()log.SetFlags(log.Llongfile)GetCommandArgs()// 打印帮助信息if helpFlag {displayHelp()os.Exit(0)}// 获取目标 IPdesIP := os.Args[len(os.Args)-1]//fmt.Println(desIP)// 构建连接conn, err := net.DialTimeout("ip:icmp", desIP, time.Duration(timeout)*time.Millisecond)if err != nil {log.Println(err.Error())return}defer conn.Close()// 远程地址remoteaddr := conn.RemoteAddr()fmt.Printf("正在 Ping %s [%s] 具有 %d 字节的数据:\n", desIP, remoteaddr, size)for i := 0; i < count; i++ {// 构建请求icmp := &ICMP{Type:        typ,Code:        code,CheckSum:    uint16(0),ID:          uint16(i),SequenceNum: uint16(i),}// 将请求转为二进制流var buffer bytes.Bufferbinary.Write(&buffer, binary.BigEndian, icmp)// 请求的数据data := make([]byte, size)// 将请求数据写到 icmp 报文头后buffer.Write(data)data = buffer.Bytes()// ICMP 请求签名(校验和):相邻两位拼接到一起,拼接成两个字节的数checkSum := checkSum(data)// 签名赋值到 data 里data[2] = byte(checkSum >> 8)data[3] = byte(checkSum)startTime := time.Now()// 设置超时时间conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))// 将 data 写入连接中,n, err := conn.Write(data)if err != nil {log.Println(err)continue}// 发送数 ++SendCnt++// 接收响应buf := make([]byte, 1024)n, err = conn.Read(buf)//fmt.Println(data)if err != nil {log.Println(err)continue}// 接受数 ++RecCnt++//fmt.Println(n, err) // data:64,ip首部:20,icmp:8个 = 92 个// 打印信息t := time.Since(startTime).Milliseconds()fmt.Printf("来自 %d.%d.%d.%d 的回复:字节=%d 时间=%d TTL=%d\n", buf[12], buf[13], buf[14], buf[15], n-28, t, buf[8])MaxTime = Max(MaxTime, t)MinTime = Min(MinTime, t)SumTime += ttime.Sleep(time.Second)}fmt.Printf("\n%s 的 Ping 统计信息:\n", remoteaddr)fmt.Printf("    数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%.f%% 丢失),\n", SendCnt, RecCnt, count*2-SendCnt-RecCnt, float64(count*2-SendCnt-RecCnt)/float64(count*2)*100)fmt.Println("往返行程的估计时间(以毫秒为单位):")fmt.Printf("    最短 = %d,最长 = %d,平均 = %d\n", MinTime, MaxTime, SumTime/int64(count))
}// 求校验和
func checkSum(data []byte) uint16 {// 第一步:两两拼接并求和length := len(data)index := 0var sum uint32for length > 1 {// 拼接且求和sum += uint32(data[index])<<8 + uint32(data[index+1])length -= 2index += 2}// 奇数情况,还剩下一个,直接求和过去if length == 1 {sum += uint32(data[index])}// 第二部:高 16 位,低 16 位 相加,直至高 16 位为 0hi := sum >> 16for hi != 0 {sum = hi + uint32(uint16(sum))hi = sum >> 16}// 返回 sum 值 取反return uint16(^sum)
}// GetCommandArgs 命令行参数
func GetCommandArgs() {flag.Int64Var(&timeout, "w", 1000, "请求超时时间")flag.IntVar(&size, "l", 32, "发送字节数")flag.IntVar(&count, "n", 4, "请求次数")flag.BoolVar(&helpFlag, "h", false, "显示帮助信息")flag.Parse()
}func Max(a, b int64) int64 {if a > b {return a}return b
}func Min(a, b int64) int64 {if a < b {return a}return b
}func displayHelp() {fmt.Println(`选项:-n count       要发送的回显请求数。-l size        发送缓冲区大小。-w timeout     等待每次回复的超时时间(毫秒)。-h            帮助选项`)
}

小结

​    本文讲解了常用工具 Ping,并且从 ICMP 报文角度手把手教大家实现了一个简易版的 Ping 工具,在这个过程中大家可以收获到很多东西,希望大家能够自己动手实现一下,结果一定不会让你失望。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/126099.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中文编程软件怎么下载,中文编程语言

中文编程软件怎么下载&#xff0c;中文编程语言 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;想如图这个实例就是用这个工具…

【MySQL】视图

文章目录 一. 基本使用二. 视图规则和限制结束语 视图是一个虚拟表&#xff0c;其内容由查询定义&#xff0c;同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。 视图的数据变化会影响到基表&#xff0c;基表的数据变化也会影响到视图 一. 基本使用 创建视图 …

Linux系统jdkTomcatMySQL安装以及J2EE后端接口部署

目录 一、jdk&Tomcat安装 1.1 上传安装包到服务器 1.2 解压对应工具包 1.3 配置环境变量并测试jdk安装 1.4 启动tomcat 1.5 防火墙设置 1.5.1 开启/关闭防火墙以及防火墙状态查看 1.5.2 开放防火墙端口 二、MySQL安装 2.1 卸载mariadb 2.2 在线下载MySQL安装包(…

C++标准模板(STL)- 类型支持 (类型特性,is_union,is_class,is_function)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

PCL setCameraPosition 参数讲解

setCameraPosition 的原型如下void setCameraPosition (double pos_x, double pos_y, double pos_z,double view_x, double view_y, double view_z,double up_x, double up_y, double up_z, int viewport 0);pos_x pos_y pos_z为相机所在的位置view_x view_y view_z 是焦点所…

用前端框架Bootstrap的AdminLTE模板和Django实现后台首页的页面

承接博文 用前端框架Bootstrap和Django实现用户注册页面 继续开发实现 后台首页的页面。 01-下载 AdminLTE-3.1.0-rc 并解压缩 以下需要的四个文件夹及里面的文件百度网盘下载链接&#xff1a; https://pan.baidu.com/s/1QYpjOfSBJPmjmVuFZdSgFQ?pwdo9ta 下载 AdminLTE-3.1…

十种排序算法(1) - 准备测试函数和工具

1.准备工作 我们先写一堆工具&#xff0c;后续要用&#xff0c;不然这些写在代码里可读性巨差 #pragma once #include<stdio.h>//为C语言定义bool类型 typedef int bool; #define false 0 #define true 1//用于交互a和b inline void swap(int* a, int* b) {/*int c *a…

机器人入门(四)—— 创建你的第一个虚拟小车

机器人入门&#xff08;四&#xff09;—— 创建你的第一个虚拟小车 一、小车建立过程1.1 dd_robot.urdf —— 建立身体1.2 dd_robot2.urdf —— 添加轮子1.3 dd_robot3.urdf —— 添加万向轮1.4 dd_robot4.urdf —— 添加颜色1.5 dd_robot5.urdf —— 添加碰撞检测(Collision …

在pycharm中,远程操作服务器上的jupyter notebook

一、使用场景 现在我们有两台电脑&#xff0c;一台是拥有高算力的服务器&#xff0c;另一台是普通的轻薄笔记本电脑。如何在服务器上运行jupyter notebook&#xff0c;同时映射到笔记本电脑上的pycharm客户端中进行操作呢&#xff1f; 二、软件 pycharm专业版&#xff0c;jupy…

打造美团外卖新体验,HarmonyOS SDK持续赋能开发者共赢鸿蒙生态

从今年8月起&#xff0c;所有升级到HarmonyOS 4的手机用户在美团外卖下单后&#xff0c;可通过屏幕上的一个“小窗口”&#xff0c;随时追踪到“出餐、取餐、送达”等订单状态。这个能让用户实时获悉订单进度的神奇“小窗口”&#xff0c;就是实况窗功能。 实况窗&#xff1a;简…

java版直播商城平台规划及常见的营销模式 电商源码/小程序/三级分销+商城免费搭建

涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis …

843. n-皇后问题

文章目录 QuestionIdeasCode Question n− 皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n &#xff0c;请你输出所有的满足条件的棋子摆法。 输入格…

心理咨询预约小程序

随着微信小程序的日益普及&#xff0c;越来越多的人开始关注如何利用小程序来提供便捷的服务。对于心理咨询行业来说&#xff0c;搭建一个心理咨询预约小程序可以大大提高服务的效率和用户体验。本文以乔拓云平台为例&#xff0c;详细介绍如何轻松搭建一个心理咨询预约小程序。…

辅助驾驶功能开发-功能规范篇(22)-6-L2级辅助驾驶方案功能规范

1.3.6 ELK 系统功能定义 ELK全称Emergency Lane Keeping,即紧急车道保持。当车辆与道路边界护栏(包含隔离带护栏、路锥、水马) 有碰撞危险或当车辆正偏出道路边沿且存在“对向来车”或“后向来车”与本车有碰撞风险时,通过给与转向反力矩进行车辆横向运动介入控制,避免或减…

态路小课堂丨如何为QSFP-DD800G光模块选择合适的光纤跳线

TARLUZ态路 在之前2篇文章中&#xff0c;我们介绍了关于800G光模块的封装、光口类型等内容。本文态路通信将为您介绍“如何为QSFP-DD800G光模块选择合适的光纤跳线”。方便您在800G数据中心网络互连场景中快速完成链路布线。 文章1&#xff1a; 三款800G光模块方案介绍—SR8/…

【AIFEM案例操作】水泵强度分析

AIFEM是由天洑自主研发的一款通用的智能结构仿真软件&#xff0c;助力用户解决固体结构相关的静力学、动力学、振动、热力学等实际工程问题&#xff0c;软件提供高效的前后处理工具和高精度的有限元求解器&#xff0c;帮助用户快速、深入地评估结构的力学性能&#xff0c;加速产…

写博客的模板

本人详解 作者&#xff1a;王文峰&#xff0c;参加过 CSDN 2020年度博客之星&#xff0c;《Java王大师王天师》作者 公众号&#xff1a;山峯草堂&#xff0c;非技术多篇文章&#xff0c;专注于天道酬勤的 Java 开发问题、中国国学、传统文化和代码爱好者的程序人生&#xff0c;…

总感觉戴助听器耳朵又闷又堵怎么办?

随着助听器技术的进步发展&#xff0c;这些问题都有了一定程度的改善。例如&#xff0c;现在的助听器变得越来越小巧&#xff0c;外形更加美观和隐蔽&#xff1b;各种降噪技术和验配技巧也提升了助听器的音质和清晰度。 但是&#xff0c;还有一个问题困扰着很多助听器用户&…

linux的使用学习(1)

Linux 修改root密码 1.以 root 用户或具有 sudo 权限的登录到 Linux 系统。 2.打终端&#xff0c;并执行以下命令以更改 root 用户的密码&#xff1a; sudo passwd root 3.然后&#xff0c;系统会要求你输入新的 root 密码。请注意&#xff0c;在输入密码时&#xff0c;终端界…

Mac docker+vscode

mac 使用docker vs code 通过vscode 可以使用docker容器的环境。 可以在容器安装gdb, 直接调试代码。 创建容易时候可以指定目录和容易目录可以共享文件。