用Go实现Ping操作 您所在的位置:网站首页 报文标识号有什么用处 用Go实现Ping操作

用Go实现Ping操作

2024-07-11 19:10| 来源: 网络整理| 查看: 265

这次我们来看一下什么是 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 报文头 和 数据包组成,其报文头包含 Type、Code、Checksum、ID、SequenceNum 字段。因此,我们需要先在本地主机上定义 ICMP 请求报文结构体:

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

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

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

var (     helpFlag bool     timeout  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  1 {         // 拼接且求和         sum += uint32(data[index]) 16     for 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 main import (     "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 = 0 var (     helpFlag bool     timeout  int64 // 耗时     size     int   // 大小     count    int   // 请求次数     typ      uint8 = 8     code     uint8 = 0     SendCnt  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 // ID     SequenceNum uint16 // 序号 } func main() {     fmt.Println()     log.SetFlags(log.Llongfile)     GetCommandArgs()     // 打印帮助信息     if helpFlag {         displayHelp()         os.Exit(0)     }     // 获取目标 IP     desIP := 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 > 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 += t         time.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 := 0     var sum uint32     for length > 1 {         // 拼接且求和         sum += uint32(data[index]) 16     for 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 


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有