首頁  >  文章  >  後端開發  >  手把手教你用Go語言打造一款簡易TCP埠掃描器

手把手教你用Go語言打造一款簡易TCP埠掃描器

Go语言进阶学习
Go语言进阶学习轉載
2023-07-24 15:33:201642瀏覽

TCP掃描本質

我們在使用TCP進行連線時,需要知道對方機器ip:port

#正常握手

##連接成功的話,流程如下。

手把手教你用Go語言打造一款簡易TCP埠掃描器#

連線失敗

有正常,就有#,如果被連接方關閉的話,流程如下。

手把手教你用Go語言打造一款簡易TCP埠掃描器

如果有防火牆

#還有一種可能是, #連接埠開放,但是防火牆攔截,##流程如下。

手把手教你用Go語言打造一款簡易TCP埠掃描器

程式碼

#本質理解之後,就可以開始擼程式碼了。

在Go中,我們通常使用net.Dial進行# TCP連線

它就兩種情況

##:返回conn。

失敗

:

err != nil


普通版

手把手教你用Go語言打造一款簡易TCP埠掃描器

# 相對來說,剛開始時,我們可能都不是太膽大,都是先寫原型,也不考慮性能。

###程式碼############
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)
}

执行结果

手把手教你用Go語言打造一款簡易TCP埠掃描器

其實是同時開啟了6W多個執行緒,去掃描每個ip:port

所以耗時最長的執行緒結束的時間,就是程式結束的時間。

感覺還行,20s 掃描完6w多個連接埠! ! !

線程池版

上面我們簡單粗暴的方式為每個ip:port都建立了一個協程。

雖然在Go中,理論上協程開個幾十萬個都沒問題,但是還是有一些壓力 #的。

所以我们应该采用一种相对节约的方式进行精简代码,一般采用线程池方式。


本次使用的线程池包:gohive

地址:https://github.com/loveleshsharma/gohive

简单介绍

手把手教你用Go語言打造一款簡易TCP埠掃描器

代码

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)
}

执行结果

手把手教你用Go語言打造一款簡易TCP埠掃描器

我设置的线程池大小是7w个,所以也是一下子开启6w多个协程的,但是我们已经可以进行线程大小约束了。


假設現在有這樣的去求,有100個ip,需要掃描每個ip開放的連接埠,如果採用簡單粗暴開啟執行緒的方式.

那就是100 65535=6552300,600多w個執行緒,還是比較消耗記憶體的,可能係統就會崩潰,如果採用執行緒池方式。

將執行緒池控制在50w個,或許情況就會好很多。

但有一點的是,在Go中,執行緒池通常需要配合#chan使用,可能需要不錯的基礎。

總結

本篇更偏向於樂趣篇,了解 好玩的玩意

其實還可以透過net.DialTimeout連接ip:port ,這個可以設定超時時間,例如超時5s就判定連接埠未開放。

這裡就不做舉例了。

咱們主要使用三種方式來實作功能。

  • 正常#版,沒有並發,速度很慢。

  • 多重協程版,並發,效能很高,但是協程太多可能會崩潰。

  • 協程池版,並發,效能高,協程數量可控。

通常情況下,如果基礎可以,更#推薦使用協程池方式。

#

以上是手把手教你用Go語言打造一款簡易TCP埠掃描器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Go语言进阶学习。如有侵權,請聯絡admin@php.cn刪除