我們在使用TCP進行連線時,需要知道對方機器的ip:port
##連接成功的話,流程如下。
#
有正常,就有#,如果被連接方關閉的話,流程如下。
#還有一種可能是, #連接埠開放,但是防火牆攔截,##流程如下。
#本質理解之後,就可以開始擼程式碼了。
在Go中,我們通常使用net.Dial進行# TCP連線。
它就兩種情況
失敗
:
err != nil。
# 相對來說,剛開始時,我們可能都不是太膽大,都是先寫原型,也不考慮性能。
package main import ( "fmt" "net" ) func main() { var ip = "192.168.43.34" for i := 21; i <= 120; i++ { var address = fmt.Sprintf("%s:%d", ip, i) conn, err := net.Dial("tcp", address) if err != nil { fmt.Println(address, "是关闭的") continue } conn.Close() fmt.Println(address, "打开") } }############執行結果####### ####################但是這個過程是非常緩慢的。 ##########
因为net.Dial
如果连接的是未开放的端口,一个端口可能就是20s+,所以,我们为什么学习多线程懂了把!!!
上述是通过循环去一个个连接ip:port
的,那我们就知道了,在一个个连接的位置,让多个人去干就好了。
所以,多线程如下。
代码
package main import ( "fmt" "net" "sync" "time" ) func main() { var begin =time.Now() //wg var wg sync.WaitGroup //ip var ip = "192.168.99.112" //var ip = "192.168.43.34" //循环 for j := 21; j <= 65535; j++ { //添加wg wg.Add(1) go func(i int) { //释放wg defer wg.Done() var address = fmt.Sprintf("%s:%d", ip, i) //conn, err := net.DialTimeout("tcp", address, time.Second*10) conn, err := net.Dial("tcp", address) if err != nil { //fmt.Println(address, "是关闭的", err) return } conn.Close() fmt.Println(address, "打开") }(j) } //等待wg wg.Wait() var elapseTime = time.Now().Sub(begin) fmt.Println("耗时:", elapseTime) }
执行结果
其實是同時開啟了6W多個執行緒,去掃描每個ip:port
。
所以耗時最長的執行緒結束的時間,就是程式結束的時間。
感覺還行,20s 掃描完6w多個連接埠! ! !
上面我們簡單粗暴的方式為每個ip:port
都建立了一個協程。
雖然在Go中,理論上協程開個幾十萬個都沒問題,但是還是有一些壓力 #的。
所以我们应该采用一种相对节约的方式进行精简代码,一般采用线程池方式。
本次使用的线程池包:gohive
地址:https://github.com/loveleshsharma/gohive
简单介绍
代码
package main //线程池方式 import ( "fmt" "github.com/loveleshsharma/gohive" "net" "sync" "time" ) //wg var wg sync.WaitGroup //地址管道,100容量 var addressChan = make(chan string, 100) //工人 func worker() { //函数结束释放连接 defer wg.Done() for { address, ok := <-addressChan if !ok { break } //fmt.Println("address:", address) conn, err := net.Dial("tcp", address) //conn, err := net.DialTimeout("tcp", address, 10) if err != nil { //fmt.Println("close:", address, err) continue } conn.Close() fmt.Println("open:", address) } } func main() { var begin = time.Now() //ip var ip = "192.168.99.112" //线程池大小 var pool_size = 70000 var pool = gohive.NewFixedSizePool(pool_size) //拼接ip:端口 //启动一个线程,用于生成ip:port,并且存放到地址管道种 go func() { for port := 1; port <= 65535; port++ { var address = fmt.Sprintf("%s:%d", ip, port) //将address添加到地址管道 //fmt.Println("<-:",address) addressChan <- address } //发送完关闭 addressChan 管道 close(addressChan) }() //启动pool_size工人,处理addressChan种的每个地址 for work := 0; work < pool_size; work++ { wg.Add(1) pool.Submit(worker) } //等待结束 wg.Wait() //计算时间 var elapseTime = time.Now().Sub(begin) fmt.Println("耗时:", elapseTime) }
执行结果
我设置的线程池大小是7w个,所以也是一下子开启6w多个协程的,但是我们已经可以进行线程大小约束了。
假設現在有這樣的去求,有100個ip
,需要掃描每個ip
開放的連接埠,如果採用簡單粗暴開啟執行緒的方式.
那就是100 65535=6552300
,600多w個執行緒,還是比較消耗記憶體的,可能係統就會崩潰,如果採用執行緒池方式。
將執行緒池控制在50w個,或許情況就會好很多。
但有一點的是,在Go中,執行緒池通常需要配合#chan
使用,可能需要不錯的基礎。
本篇更偏向於樂趣篇,了解 好玩的玩意。
其實還可以透過net.DialTimeout
連接ip:port
,這個可以設定超時時間,例如超時5s就判定連接埠未開放。
這裡就不做舉例了。
咱們主要使用三種方式來實作功能。
正常#版,沒有並發,速度很慢。
多重協程版,並發,效能很高,但是協程太多可能會崩潰。
協程池版,並發,效能高,協程數量可控。
通常情況下,如果基礎可以,更#推薦使用協程池方式。
以上是手把手教你用Go語言打造一款簡易TCP埠掃描器的詳細內容。更多資訊請關注PHP中文網其他相關文章!