SoFunction
Updated on 2025-03-05

golang high concurrent current limit operation ping / telnet

need

When you need to ping/telnet multiple IPs at the same time, you can implement it by introducing ping package/telnet package, or you can call the cmd command by go. However, the latter has poor call efficiency, so you can choose ping package and telnet package here.

There is also the problem of high concurrency. High concurrency can be achieved through shell scripts or go, so I chose to use the coroutines that come with go, but if you want to process 1,000+ IPs at the same time, considering the performance of the machine, ratelimit needs to control the number of go coroutines developed. Here I will mainly write my suggestions and the pitfalls that have been passed.

ping

Reference link:/sparrc/go-ping

import "/sparrc/go-ping"
import "time"
func (p *Ping) doPing(timeout , count int, ip string) (err error) {
 pinger, cmdErr := (ip)
 if cmdErr != nil {
  ("Failed to ping " + )
  err = cmdErr
  return
 }
  = count
  = 
  = timeout
 // If true, it means a standard icmp package, false means that packets can be lost like udp (false)
 // implement () 
 // Get the return information after ping stats := () 
 //Delay latency = float64() 
 // Standard total round trip time jitter = float64() 
 //Packet loss rate packetLoss =  
 return
}

Note: () This is blocked when executing here. If the concurrency amount is large, the program will be stuck here. Therefore, when there is a high concurrency requirement, it is recommended to deal with the following:

go ()

(timeout)

telnet

package main
import (
 "/reiver/go-telnet"
)
func doTelnet(ip string, port int) {
 var caller  = 
 address := ip + ":"+ (port)
 // DialToAndCall checks connectivity and calls (address, caller)
 }
}

An error occurred when the bug was reported:

lookup tcp/: nodename nor servname provided, or not known

solve:

Modify string(port) to (port)

The telnet cannot set the timeout time in DialToAndCall method, and the default timeout time is 1 minute, so use the DialTimeout method to implement telnet.

import "net"
func doTelnet(ip string, ports []string) map[string]string {
 // Check emqx 1883, 8083, 8080, 18083 ports results := make(map[string]string)
 for _, port := range ports {
 address := (ip, port)
 // 3 seconds timeout conn, err := ("tcp", address, 3*)
 if err != nil {
 results[port] = "failed"
 } else {
 if conn != nil {
 results[port] = "success"
 _ = ()
 } else {
 results[port] = "failed"
 }
 }
 }
 return results
}

Shell high concurrency

The essence is to read the ip in the file, then call the ping method, to achieve high concurrency, and use & traverse all ips and then hand them to the operating system to process high concurrency.

while read ip
do
 {
 doPing(ip)
 } &
done < 

Go high concurrent speed limit

import (
 "context"
 "fmt"
 "log"
 "time"
 "sync"
 "/x/time/rate"
)
func Limit(ips []string)([]string, []string, error) {
 //The first parameter is the maximum concurrency number per second, and the second parameter is the capacity of the bucket. The first time the number of executable per second is the capacity of the bucket. It is recommended that both values ​​be written as the same r := (10, 10)
 ctx := ()
 
 wg := {}
 (len(ips))
 
 lock := {}
 var success []string
 var fail []string
 
 defer ()
 for _,ip:=range ips{
 //Each consumes 2 pieces and puts one in. After consumption, it will be put in. If the initial number is 5, so when this code is executed, the inside of the barrel will be empty when it is executed for the fourth time. If there are not enough two pieces at present, it will not take it this time. Put one in and then return false err := (ctx, 2)
 if err != nil {
  (err)
 }
 go func(ip string) {
 defer func() {
 ()
 }()
 err := doPing(, 2, ip)
 ()
 defer ()
 if err != nil {
 fail = append(fail, ip)
 return
 } else {
 success = append(success, ip)
 }
 }(ip)
 }
 // wait for all go coroutines to end ()
 return success,fail,nil
}
func main() {
 ips := [2]string{"192.168.1.1","192.168.1.2"}
 success,fail,err := Limit(ips)
 if err != nil {
 ("ping error")
 }
}

Pay attention to a concurrent implementation pit here. When using goroutine in the for loop, you must pass the traversal parameters in order to ensure that each traversal parameters are executed, otherwise it can only be executed once.

(Expand) Pipeline, deadlock

Let’s take a look at an example:

func main() {
 go print() // Start a goroutine print()
}
func print() {
("*******************")
}

Output result:

*******************

That's right, there is only one line, because when go creates a coroutine to execute the print method, the main function has finished printing and printing it out, so the goroutine has finished executing the program before the goroutine has been executed. Solve this problem, let the main function wait for the goroutine to be executed in the main function, or you can use the wait for the goroutine to be executed. Another type is the channel

Channels are divided into buffered channels and buffered channels

Unbuffered channel

Unbuffered channel means a channel with a length of 0. Store one data and retrieve data from the unbuffered channel. If there is no data in the channel, it will block and may also cause deadlock. The same data enters the unbuffered channel. If there is no other goroutine to take away this data, it will also block. Remember that unbuffered data does not store data.

func main() {
 var channel chan string = make(chan string)
 go func(message string) {
 channel&lt;- message // Save message }("Ping!")
 (&lt;-messages) // Get the message}

Cache Channel

As the name suggests, the cache channel can store data, and there will be no blockage between goroutines. When reading data in the for loop, you must judge the situation when there is no data in the pipeline, otherwise a deadlock will occur. See an example.

channel := make(chan int, 3)
channel &lt;- 1
channel &lt;- 2
channel &lt;- 3
// Explicitly close the channelclose(channel)
for v := range channel {
 (v)
 // If the existing data volume is 0, the loop will jump out, and the effect is the same as the explicit tunnel closing effect, just select one if len(ch) &lt;= 0 { 
 break
 }
}

But there is a problem here. The data in the channel can be stored at any time, so when we traversed, we cannot determine that the current number is the total number of channels, so it is recommended to use select listening channels

// Create a timing channeltimeout := (1 * ) 
// Listen to 3 channels of dataselect {
 case v1 := &lt;- c1: ("received %d from c1", v1)
 case v2 := &lt;- c2: ("received %d from c2", v2)
 case v3 := &lt;- c3: ("received %d from c3", v3)
 case &lt;- timeout: 
 is_timeout = true // time out break
 }
}

The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.