>  기사  >  백엔드 개발  >  手把手教你用Go语言打造一款简易TCP端口扫描器

手把手教你用Go语言打造一款简易TCP端口扫描器

Go语言进阶学习
Go语言进阶学习앞으로
2023-07-24 15:33:201627검색

ㅋㅋㅋ

상대방의 기계

정상적인 악수

연결

에 성공하면 과정은 다음과 같습니다.

연결 실패

는 정상이며, 실패가 있습니다. 연결된 당사자에 의해 가 종료된 경우 프로세스는 다음과 같습니다.

手把手教你用Go语言打造一款简易TCP端口扫描器

방화벽이 있는 경우

또 다른 가능성이 있습니다. 포트는 열려 있지만 방화벽 차단, 과정은 다음과 같습니다.

手把手教你用Go语言打造一款简易TCP端口扫描器Code


본질을 이해한 후 코딩을 시작할 수 있습니다.

Go에서는 일반적으로

을 사용하여 net.DialTCP 연결을 만듭니다.

단지 두 가지 상황

  • Success이 있습니다. conn으로 돌아갑니다.

  • 실패:err != nil.

일반 버전

비교적으로 말하면 처음에는 너무 대담하지 않을 수도 있고, 성능을 고려하지 않고 모두 프로토타입을 먼저 작성합니다.

Code

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, "打开")
  }
}

실행 결과

手把手教你用Go语言打造一款简易TCP端口扫描器

하지만 이 프로세스는 매우 느립니다.

因为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:포트ip:port

所以耗时最长的线程结束的时间,就是程序结束的时间。

感觉还行,20s+扫描完6w多个端口!!!

线程池版

上面我们简单粗暴的方式为每个ip:port.

그래서 걸리는 시간

끝나는 데 가장 긴 스레드 이 프로그램이 끝나는 시간입니다.

feeling 나쁘지 않은데, 20초 안에 60,000개의 포트가 스캔되었습니다! ! !

🎜

🎜스레드 풀 버전🎜

🎜Above us🎜🎜🎜 각 🎜🎜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개가 있습니다. ipip,需要扫描每个ip开放的端口,如果采用简单粗暴开线程的方式.

那就是100+65535=6552300,600多w个线程,还是比较消耗内存的,可能系统就会崩溃,如果采用线程池方式。

将线程池控制在50w个,或许情况就会好很多。

但是有一点的是,在Go中,线程池通常需要配合chan, 각 ip

🎜간단하고 투박한 방법을 사용하여 스레드를 여는 경우 개방형 포트입니다.🎜🎜

🎜바로 그거예요🎜🎜100+65535=6552300🎜 🎜, 600w 이상의 스레드는 여전히 많은 메모리를 소비하며 스레드 풀 방식을 사용하면 시스템이 충돌할 수 있습니다. 🎜🎜

🎜will 스레드 풀을 500,000으로 제어하면 상황이 훨씬 좋아질 수 있습니다. 🎜🎜

🎜하지만 한 가지 점은 Go에서 스레드 풀은 일반적으로 🎜🎜chan🎜🎜사용하려면 좋은 기초가 필요할 수 있습니다. 🎜🎜

요약

이 기사는 재미있는 기사에 더 가깝고 재미있는 것들에 대해 알아볼 수 있습니다.

실제로 net.DialTimeoutnet.DialTimeout连接ip:portConnectionip:port

, 이 시간 제한을 설정할 수 있습니다. 예를 들어 시간 제한이 5초이면 포트가 열려 있지 않은 것으로 판단됩니다. .

여기에서는 예를 들어주지 않겠습니다. 우리는 함수 구현을 위해 주로 세 가지 방법

    을 사용합니다.
  • Normal

  • 버전, 동시성 없음, 매우 느림.
  • 다중 코루틴

    🎜버전, 동시성, 고성능이지만 너무 많은 코루틴이 충돌할 수 있습니다. 🎜🎜
  • 코루틴 풀버전, 동시성, 고성능 및 제어 가능한 코루틴 수.

보통은 기본만 괜찮다면 코루틴 풀 방식을 사용하는 것이 더추천입니다.

위 내용은 手把手教你用Go语言打造一款简易TCP端口扫描器의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 Go语言进阶学习에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
이전 기사:다음 기사: