摘要: Ping的基本原理是发送和接受ICMP请求回显报文,利用Go语言可以轻松实现这一过程,较之C/C++语言,Go的实现过程十分简单,效率和安全性也十分完美。

引言

关于各种语言实现Ping已经是大家喜闻乐见的事情了,网络上利用Golang实现Ping已经有比较详细的代码示例,但大多是仅仅是实现了Request过程,而对Response的回显内容并没有做接收。而Ping程序不仅仅是发送一个ICMP,更重要的是如何接收并进行统计。

编码

要用到的package:

import (

"bytes"

"container/list"

"encoding/binary"

"fmt"

"net"

"os"

"time"

)

使用Golang提供的net包中的相关函数可以快速构造一个IP包并自定义其中一些关键参数,而不需要再自己手动填充IP报文。

使用encoding/binary包可以轻松获取结构体struct的内存数据并且可以规定字节序(这里要用网络字节序BigEndian),而不需要自己去转换字节序。之前的一片文中使用boost,还要自己去实现转换过程,详见:关于蹭网检查的原理及实现

使用container/list包,方便进行结果统计

使用time包实现耗时和超时处理

ICMP报文struct:

type ICMP struct {

Type uint8

Code uint8

Checksum uint16

Identifier uint16

SequenceNum uint16

}

Usage提示:

arg_num := len(os.Args)

if arg_num < 2 {

fmt.Print(

"Please runAs [super user] in [terminal].n",

"Usage:n",

"tgoping urln",

"texample: goping www.baidu.com",

)

time.Sleep(5e9)

return

}

注意这个ping程序,包括之前的ARP程序都必须使用系统最高权限执行,所以这里先给出提示,使用time.Sleep(5e9),暂停5秒,是为了使双击执行者看到提示,避免控制台一闪而过。

关键net对象的创建和初始化:

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 = 8

icmp.Code = 0

icmp.Checksum = 0

icmp.Identifier = 0

icmp.SequenceNum = 0

var buffer bytes.Buffer

binary.Write(&buffer, binary.BigEndian, icmp)

icmp.Checksum = CheckSum(buffer.Bytes())

buffer.Reset()

binary.Write(&buffer, binary.BigEndian, icmp)

仍然非常简单,利用binary可以把一个结构体数据按照指定的字节序读到缓冲区里面,计算校验和后,再读进去。

检验和算法参考上面给出的URL中的实现:

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 += 2

length -= 2

}

if length > 0 {

sum += uint32(data[index])

}

sum += (sum >> 16)

return uint16(^sum)

}

下面是Ping的Request过程,这里仿照Windows的ping,默认只进行4次:

fmt.Printf("n正在 Ping %s 具有 0 字节的数据:n", raddr.String())

recv := make([]byte, 1024)

statistic := list.New()

sended_packets := 0

for 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 的回复: 时间 = %dmsn", 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 min, max, sum int64

if statistic.Len() == 0 {

min, max, sum = 0, 0, 0

} else {

min, max, 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,平均 = %.0fmsn",

raddr.String(),

sended_packets, recved, losted, float32(losted)/float32(sended_packets)*100,

min, max, float32(sum)/float32(recved),

)

}()

统计过程注意类型的转换和格式化就行了。

全部代码就这些,执行结果大概是这个样子的:

注意每次Ping后都没有"休息",不像Windows或者Linux的会停顿几秒再Ping下一轮。

结束语

Golang实现整个Ping比我想象中的还要简单很多,静态编译速度是十分快速,相比C而言,你需要更多得了解底层,甚至要从链路层开始,你需要写更多更复杂的代码来完成相同的工作,但究其根本,C语言仍然是鼻祖,功不可没,很多原理和思想都要继承和发展,这一点Golang做的很好。

go 调用linux命令ping,使用Golang实现简单Ping过程-Go语言中文社区相关推荐

  1. mac go linux 目标,Mac下Golang安装以及目录结构-Go语言中文社区

    1.安装步骤 a.下载地址  下载SDK后,解压到自己喜欢的目录(后面配置PATH需要用到) b.配置PATH,有很多个地方可以配置具体可参考此链(PATH配置详解),我是直接修改的~/.bash_p ...

  2. Linux中硬盘smart故障,Linux 系统如何通过解读 SMART 信息(smartctl 命令输出)预测硬盘的损坏事件-Go语言中文社区...

    目录 说明 本文档介绍磁盘 S.M.A.R.T信息,在linux 上如何根据 smartctl 的输出判断磁盘是否快要损坏(即使此时smartctl 给出磁盘状态依然是 PASSED),就像windo ...

  3. golang杀死java_用Golang来保护Java程序-Go语言中文社区

    #反编译带来的困扰 对于一个开发给自己组织内部用的程序,我们是不怎么需要考虑程序被反编译的事情的,但是对于商业软件来说,这又显得有必要,毕竟国内软件行业的竞争还是非常的激烈,大家可以把竞争对手的程序搞 ...

  4. linux开启远程ssh服务,linux开启ssh服务,实现ssh远程登录-Go语言中文社区

    1.查询是否安装SSH. rpm -pa |grep ssh 2.如果没有安装rmp: sudo apt-get install rmp          #ubuntu,debian yum -y ...

  5. mysql表导出和导入命令行_MySQL命令行导出导入数据库和数据表-Go语言中文社区...

    MySQL命令行导出数据库: 1,进入MySQL目录下的bin文件夹:cd MySQL中到bin文件夹的目录 如我输入的命令行:cd C:Program FilesMySQLMySQL Server ...

  6. linux蓝牙鼠标自动断开,Manjaro Linux 连接蓝牙鼠标后重启电脑得重新连接 解决方案-Go语言中文社区...

    从Ubuntu到CentOS 再到 Deepin Linux........折腾过很多发行版,前不久给自己的老电脑安装了win10+Manjaro双系统,manjaro是基于Arch的,不过相比arc ...

  7. linux 写脚本登录ftp,Linux使用Shell脚本实现ftp的自动上传下载-Go语言中文社区

    1. ftp自动登录批量下载文件. #####从ftp服务器上的/home/data 到 本地的/home/databackup#### #!/bin/bash ftp -n< open 192 ...

  8. go调用python3_在python3中使用google的protobuf以及gRPC-Go语言中文社区

    下载源代码:地址 安装proto #下载:protoc-3.9.1-linux-x86_64.zip unzip protoc-3.9.1-linux-x86_64.zip -d protoc-3.9 ...

  9. linux选择指定显卡pcie,Linux下查看显卡PCIE速率x16x8x4及设定-Go语言中文社区

    通过 lspci 指令查看: sudo lspci -vv 在返回的结果信息中找到与自己显卡相同的型号,如下: 02:00.0 3D controller: NVIDIA Corporation GM ...

  10. linux下java命令行参数_Java调用Linux命令行

    Java调用Linux命令行 Java语言以其跨平台性和简易性而著称,在Java里面的lang包里(java.lang.Runtime)提供了一个允许Java程序与该程序所运行的环境交互的接口,这就是 ...

最新文章

  1. vba 根据分辨率 缩放显示比例_【显示百闻录】第一讲:关于屏幕尺寸、比例以及分辨率...
  2. java groovyshell_在java中使用groovy怎么搞
  3. antd 设置表头属性_解决react使用antd table组件固定表头后,表头和表体列不对齐以及配置fixed固定左右侧后行高度不对齐...
  4. 国内SAP从业者们2020年最想学习的SAP相关知识分类的调查问卷结果
  5. spring常见面试问题_Spring面试问题
  6. 蛇哥开局两星机器人视频_虎牙粉丝活动落幕,蛇哥骚男夺冠,拿下季军的吃鸡一姐直播却哭了...
  7. 开源漏洞扫描工具(OWASP-Dependency-Check)探索
  8. urtracker 项目管理工具
  9. ggmusic java证书过期_证书过期时的Java trustmanager行为
  10. python密码传参有特殊字符如何解决_am start的总结,-d参数的总结,以及python中传递内容包含中文及特殊字符的解决方案...
  11. Mac m1 安装jdk
  12. 科创板已开板 区块链离科创板还远吗?
  13. axure 8.1 破解 和 汉化
  14. 区间对比_预算10-15万元区间 国内在售街车综合实力对比
  15. Spring复习——B站
  16. 《众妙之门——网页排版设计制胜秘诀》——导读
  17. Collected errors: * opkg_conf_load: Could not lock /var/lock/opkg.lock: Resource temporarily unavail
  18. 使用ClickHouse JDBC官方驱动,踩坑无数
  19. Kafka 的 replica 同步机制(ISR与OSR列表数据相互转换)
  20. 用labview设计jk触发器_基于LabVIEW的基本触发器设计.doc

热门文章

  1. 转:C# 中 MSCHART 饼状图显示百分比
  2. Android Notification中PendingIntent.Flag的应用
  3. WPS页眉页脚怎么设置每页不同
  4. MSP430 F5529 单片机 串口 万年历 电子时钟 数字时钟 Digital clock
  5. Android数字时钟神一般的实现——TextClock
  6. 极简网页设计技巧,打造简约之美
  7. Separating Pebbles数学,暴力
  8. 一筐鸡蛋 1个1个拿……弱智题
  9. Computer composition and design work04 ——fifth verson
  10. 编程中如何不使用中间变量的情况下将两个变量的值进行交换