Introduction
Ping in various languages is already a matter of interest to everyone. Using golang on the Internet to implement Ping has provided detailed code examples, but most of them only implement the request process, the echo content of response is not received. The Ping program not only sends an ICMP packet, but also receives and collects statistics.
The following is the ping implementation code for the network:
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
This article draws on some of the Code in the second link.
Preparation
Install the latest go
If there is no VPN for Google because of the wall, download it here:
Http://www.golangtc.com/download
Using any text editor or liteide makes compilation and debugging easier.
Https://github.com/visualfc/liteide
Encoding
Package to be used:
import ("bytes""container/list""encoding/binary""fmt""net""os""time")
Using the functions in the net package provided by golang, you can quickly construct an IP package and customize some of its key parameters without manually filling in IP packets.
Using the encoding/Binary Package, you can easily obtain the memory data of the struct structure and specify the byte order (bigendian in the network byte order) without converting the byte order yourself. In the previous article, we used boost to implement the conversion process on our own. For details, see the principles and implementation of the network check.
Use the container/list package for convenient result Statistics
Use the time package for time-consuming and time-out processing
ICMP packet struct:
type ICMP struct {Type uint8Code uint8Checksum uint16Identifier uint16SequenceNum uint16}
Usage prompt:
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}
Note that this Ping program, including earlier ARP programs, must be executed with the highest system permission. Therefore, the following prompt is displayed: Use Time. sleep (5e9), pause for 5 seconds, in order to enable the double-click performer to see the prompt, to avoid a flash of the console.
Key net object creation and initialization:
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 indicates that an IP packet is generated. The version number is V4 and the Protocol is ICMP (here, the string ip4: ICMP sets the protocol field of the IP packet to 1, indicating ICMP ),
The source address laddr can be 0.0.0.0 or its own IP address, which does not affect ICMP operations.
The destination address raddr is a URL. Here, resolve is used for DNS resolution. Note that the returned value is a pointer. Therefore, the parameters in the dialip method below indicate that there is no URL operator.
So that a complete IP packet is assembled, and we have not worried about other fields in the IP address. Go has already handled it for us.
The returned conn * Net. ipconn object can be used for subsequent operations.
Defer conn. Close () indicates that the function will be executed at return and will not be closed.
The ICMP packet needs to be constructed below:
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)
It is still very simple. using binary, you can read a struct data in the buffer in the specified byte order, calculate the checksum, and then read it.
For the validation algorithm, refer to the implementation in the above 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)}
The following is the ping request process, which is equivalent to Windows ping. By default, only four requests are performed:
FMT. printf ("\ n pinging % s data with 0 bytes: \ 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 ("request timeout") Continue} t_end: = time. now () dur: = t_end.sub (t_start ). nanoseconds ()/1e6fmt. printf ("reply from % s: time = % 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 ("")}
"Data with 0 bytes" indicates that no data field exists in the ICMP message, which is slightly different from 32 bytes in windows.
After the conn. Write method is executed, an ICMP request is sent for timing and count at the same time.
Conn. setreaddeadline can stop the read wait within the specified time when no data is received, Return Error err, and determine that the request times out. Otherwise, after receiving the response, calculate the time used for back-and-forth, and add a list to facilitate subsequent statistics.
The comments part of the content is the code I used to explore the returned data. You can try to see which data packet is read?
The statistics will be executed at the end of the loop. Here, defer is used to press Ctrl + C to execute the return statement. However, the console is really not powerful and it will be killed directly ..
Defer func () {FMT. println ("") // Information Statistics 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 statistics: \ n packet: Sent = % d, received = % d, lost = % d (%. 1f % lost), \ n round-trip estimated time (in milliseconds): \ n shortest = % DMS, longest = % DMS, average = %. 0fms \ n ", raddr. string (), sended_packets, recved, losted, float32 (losted)/float32 (sended_packets) * 100, Min, Max, float32 (SUM)/float32 (recved ),)}()
During the statistics process, you only need to convert and format the data types.
All the code is like this, And the execution result is like this:
Note that there is no "rest" after each Ping, unlike windows or Linux, the Ping will pause for several seconds and then ping the next round.
Ending
Golang's ping implementation is much simpler than I thought, and the static Compilation speed is very fast. Compared with C, you need to know more about the underlying layer, or even start from the link layer, you need to write more and more complex code to complete the same job. However, at its root, C language is still the originator of C, and many principles and ideas must be inherited and developed, golang is doing well.