ljymoonlight

导航

golang ping 包

### ping包监听需求 

 

直接在PC上ping 就可以确定调试的设备是否在线。 但是而且不能满足我的一个小小要求,就是当

设备不在线的时候,又不能够主动通知我。 因此就需要通过自己写程序实现该功能,在shell上写

脚本实现简单的通知效果这是没有什么问题。当然在bat中也是一样能够实现,但是bat编程写起来

真的是不习惯。选择用golang 主要还是个人觉得方便。  

注: 内容是网上复制整理  ,很懒,只是为了方便个人下次使用时可以找到相关函数,当前并没有很好的整理 。 

type ICMP struct {

Type        uint8 Code        uint8 Checksum    uint16 Identifier  uint16 SequenceNum uint16 }

arg_num := len(os.Args) if arg_num < 2 { fmt.Print( "Please runAs [super user] in [terminal].\n", "Usage:\n", "\tgoping url\n", "\texample: goping www.baidu.com", ) time.Sleep(5e9) return }

var ( icmp     ICMP laddr    = net.IPAddr{IP: net.ParseIP("0.0.0.0")} raddr, _ = net.ResolveIPAddr("ip", os.Args[1]) ) conn, err := net.DialIP("ip4:icmp", &laddr, raddr) if err != nil { fmt.Println(err.Error()) return } defer conn.Close()

 

net.DialIP表示生成一个IP报文,版本号是v4,协议是ICMP(这里字符串ip4:icmp会把IP报文的协议字段设为1表示ICMP协议),

源地址laddr可以是0.0.0.0也可以是自己的ip,这个并不影响ICMP的工作。

目的地址raddr是一个URL,这里使用Resolve进行DNS解析,注意返回值是一个指针,所以下面的DialIP方法中参数表示没有取地址符。

这样一个完整的IP报文就装配好了,我们并没有去操心IP中的其他一些字段,Go已经为我们处理好了。

通过返回的conn *net.IPConn对象可以进行后续操作。

defer conn.Close() 表示该函数将在Return时被执行,确保不会忘记关闭。

下面需要构造ICMP报文了:

icmp.Type = 8icmp.Code = 0icmp.Checksum = 0icmp.Identifier = 0icmp.SequenceNum = 0var buffer bytes.Bufferbinary.Write(&buffer, binary.BigEndian, icmp)icmp.Checksum = CheckSum(buffer.Bytes())buffer.Reset()binary.Write(&buffer, binary.BigEndian, icmp)

func CheckSum(data []byte) uint16 { var ( sum    uint32 length int = len(data) index  int ) for length > 1 { sum += uint32(data[index])<<8 + uint32(data[index+1]) index += 2length -= 2 } if length > 0 { sum += uint32(data[index]) } sum += (sum >> 16) return uint16(^sum) }

fmt.Printf("\n正在 Ping %s 具有 0 字节的数据:\n", raddr.String()) recv := make([]byte1024) statistic := list.New() sended_packets := 0for i := 4; i > 0; i-- { if _, err := conn.Write(buffer.Bytes()); err != nil { fmt.Println(err.Error()) return } sended_packets++ t_start := time.Now() conn.SetReadDeadline((time.Now().Add(time.Second * 5))) _, err := conn.Read(recv) if err != nil { fmt.Println("请求超时") continue } t_end := time.Now() dur := t_end.Sub(t_start).Nanoseconds() / 1e6 fmt.Printf("来自 %s 的回复: 时间 = %dms\n", raddr.String(), dur) statistic.PushBack(dur) //for i := 0; i < recvsize; i++ {// if i%16 == 0 {// fmt.Println("")// }// fmt.Printf("%.2x ", recv[i])//}//fmt.Println("") }

"具有0字节的数据"表示ICMP报文中没有数据字段,这和Windows里面32字节的数据的略有不同。

conn.Write方法执行之后也就发送了一条ICMP请求,同时进行计时和计次。

conn.SetReadDeadline可以在未收到数据的指定时间内停止Read等待,并返回错误err,然后判定请求超时。否则,收到回应后,计算来回所用时间,并放入一个list方便后续统计。

注释部分内容是我在探索返回数据时的代码,读者可以试试看Read到的数据是哪个数据包的?

统计工作将在循环结束时进行,这里使用了defer其实是希望按了Ctrl+C之后能return执行,但是控制台确实不给力,直接给杀掉了。。

 

defer func() { fmt.Println("") //信息统计var minmax, sum int64 if statistic.Len() == 0 { minmax, sum = 000else { minmax, sum = statistic.Front().Value.(int64), statistic.Front().Value.(int64), int64(0) } for v := statistic.Front(); v != nil; v = v.Next() { val := v.Value.(int64) switch { case val < min: min = val case val > max: max = val } sum = sum + val } recved, losted := statistic.Len(), sended_packets-statistic.Len() fmt.Printf("%s 的 Ping 统计信息:\n  数据包:已发送 = %d,已接收 = %d,丢失 = %d (%.1f%% 丢失),\n往返行程的估计时间(以毫秒为单位):\n  最短 = %dms,最长 = %dms,平均 = %.0fms\n", raddr.String(), sended_packets, recved, losted, float32(losted)/float32(sended_packets)*100, minmax, float32(sum)/float32(recved), ) }()

 

 

 

type ICMP struct { Type        uint8 Code        uint8 Checksum    uint16 Identifier  uint16 SequenceNum uint16 }

posted on 2021-02-28 14:36  ljymoonlight  阅读(217)  评论(0编辑  收藏  举报