使用Golang實現簡單Ping過程

來源:互聯網
上載者:User

標籤:style   blog   http   使用   os   strong   資料   for   

引言

    關於各種語言實現Ping已經是大家喜聞樂見的事情了,網路上利用Golang實現Ping已經有比較詳細的程式碼範例,但大多是僅僅是實現了Request過程,而對Response的回顯內容並沒有做接收。而Ping程式不僅僅是發送一個ICMP,更重要的是如何接收並進行統計。

    下面是網路上幾篇關於Ping的實現代碼:

    https://github.com/paulstuart/ping/blob/master/ping.go

    http://blog.csdn.net/gophers/article/details/21481447

    http://blog.csdn.net/laputa73/article/details/17226337

本文借鑒了第二個連結裡面的部分代碼。

準備

  1. 安裝最新的Go

    由於Google被牆的原因,如果沒有VPN的話,就到這裡下載:

    http://www.golangtc.com/download

  2. 使用任意文字編輯器,或者LiteIDE會比較方便編譯和調試,下面是LiteIDE的

    https://github.com/visualfc/liteide

編碼

要用到的package:

import ("bytes""container/list""encoding/binary""fmt""net""os""time")

  1. 使用Golang提供的net包中的相關函數可以快速構造一個IP包並自訂其中一些關鍵參數,而不需要再自己手動填充IP報文。

  2. 使用encoding/binary包可以輕鬆擷取結構體struct的記憶體資料並且可以規定位元組序(這裡要用網路位元組序BigEndian),而不需要自己去轉換位元組序。之前的一片文中使用boost,還要自己去實現轉換過程,詳見:關於蹭網檢查的原理及實現

  3. 使用container/list包,方便進行結果統計

  4. 使用time包實現耗時和逾時處理

ICMP報文struct:

type ICMP struct {Type        uint8Code        uint8Checksum    uint16Identifier  uint16SequenceNum uint16}

Usage提示:

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}

注意這個ping程式,包括之前的ARP程式都必須使用系統最高許可權執行,所以這裡先給出提示,使用time.Sleep(5e9),暫停5秒,是為了使雙擊執行者看到提示,避免控制台一閃而過。

關鍵net對象的建立和初始化:

var (icmp     ICMPladdr    = 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)

仍然非常簡單,利用binary可以把一個結構體資料按照指定的位元組序讀到緩衝區裡面,計算校正和後,再讀進去。

檢驗和演算法參考上面給出的URL中的實現:

func CheckSum(data []byte) uint16 {var (sum    uint32length 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)}

下面是Ping的Request過程,這裡仿照Windows的ping,預設只進行4次:

fmt.Printf("\n正在 Ping %s 具有 0 位元組的資料:\n", raddr.String())recv := make([]byte, 1024)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() / 1e6fmt.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 min, max, sum int64if 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 = valcase 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,min, max, float32(sum)/float32(recved),)}()

統計過程注意類型的轉換和格式化就行了。

全部代碼就這些,執行結果大概是這個樣子的:

注意每次Ping後都沒有"休息",不像Windows或者Linux的會停頓幾秒再Ping下一輪。

收尾

Golang實現整個Ping比我想象中的還要簡單很多,靜態編譯速度是十分快速,相比C而言,你需要更多得瞭解底層,甚至要從鏈路層開始,你需要寫更多更複雜的代碼來完成相同的工作,但究其根本,C語言仍然是鼻祖,功不可沒,很多原理和思想都要繼承和發展,這一點Golang做的很好。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.