用 Go 语言打造高效 TCP 扫描器:从入门到并发优化

用Go语言打造高效TCP扫描器:从入门到并发优化你是否好奇网络端口是如何被探测的?或者想亲手打造一个工具来扫描服务器的开放端口?Go语言以其简洁的语法和强大的并发能力,成为实现网络工具的绝佳选择。在这篇文章中,我们将从TCP三次握手的基础讲起,手把手带你用Go编写一个TCP扫

用 Go 语言打造高效 TCP 扫描器:从入门到并发优化

你是否好奇网络端口是如何被探测的?或者想亲手打造一个工具来扫描服务器的开放端口?Go 语言以其简洁的语法和强大的并发能力,成为实现网络工具的绝佳选择。在这篇文章中,我们将从 TCP 三次握手的基础讲起,手把手带你用 Go 编写一个 TCP 扫描器,从简单的顺序扫描,到利用 goroutine 的并发版本,再到优化后的 worker 池模型。无论你是网络编程新手还是 Go 语言爱好者,这篇干货都能让你有所收获。准备好了吗?让我们一起用代码探索网络世界!

本文详细介绍了如何用 Go 语言实现 TCP 扫描器。开篇解释了 TCP 协议和三次握手的基本原理,随后通过代码展示了非并发版本的扫描器实现。接着,文章引入 goroutine 实现并发扫描,并通过 sync.WaitGroup 提升效率。为了进一步优化,展示了基于 worker 池的并发模型,利用通道(channel)管理任务分发和结果收集,最后优化为排序输出开放和关闭端口。代码由浅入深,注释清晰,适合学习 Go 并发编程和网络开发的读者。通过本文,你将掌握 Go 在网络工具开发中的核心技巧。

Go 语言编写 TCP 扫描器

TCP

  • TCP,也就是传输控制协议(Transmission Control Protocol)。

TCP握手

  • 建立 TCP连接(或者叫打开端口),需要3次握手

客户端 -> 端口打开 ->服务器

  1. syn (请求建立新连接)
  2. syn-ack (同意创建新连接)
  3. ack (表示响应)
  • 服务端端口关闭 Closed Port
    • client -syn-> Server
    • Server -rst-> Client
  • 如果存在防火墙 Filtered Port
    • Client —syn (Timeout)— Firewall Server

非并发的 TCP 扫描器

创建目录并在该目录创建main.go 文件

~/Code/go via 🐹 v1.20.3 via 🅒 base
➜ mcd tcp-scanner

Code/go/tcp-scanner via 🐹 v1.20.3 via 🅒 base
➜ go mod init
go: cannot determine module path for source directory /Users/qiaopengjun/Code/go/tcp-scanner (outside GOPATH, module path must be specified)

Example usage:
 'go mod init example.com/m' to initialize a v0 or v1 module
 'go mod init example.com/m/v2' to initialize a v2 module

Run 'go help mod init' for more information.

Code/go/tcp-scanner via 🐹 v1.20.3 via 🅒 base
➜ go mod init tcp-scanner
go: creating new go.mod: module tcp-scanner

Code/go/tcp-scanner via 🐹 v1.20.3 via 🅒 base
➜ c

Code/go/tcp-scanner via 🐹 v1.20.3 via 🅒 base
➜

main.go 文件

package main

import (
 "fmt"
 "net"
)

func main() {
 for i := 21; i < 120; i++ {
  address := fmt.Sprintf("20.194.168.28:%d", i)
  conn, err := net.Dial("tcp", address)
  if err != nil {
   fmt.Printf("%s failed 关闭了\n", address)
   continue
  }
  conn.Close()
  fmt.Printf("%s connected 打开了!!!\n", address)
 }
}

并发的 TCP 扫描器

package main

import (
 "fmt"
 "net"
 "sync"
 "time"
)

func main() {
 start := time.Now()
 var wg sync.WaitGroup
 for i := 21; i < 120; i++ {
  wg.Add(1)
  go func(j int) {
   defer wg.Done()
   address := fmt.Sprintf("20.194.168.28:%d", j)
   conn, err := net.Dial("tcp", address)
   if err != nil {
    fmt.Printf("%s 关闭了\n", address)
    return
   }
   conn.Close()
   fmt.Printf("%s 打开了!!!\n", address)
  }(i)
 }
 wg.Wait()
 elapsed := time.Since(start) / 1e9
 fmt.Printf("\n\n%d seconds", elapsed)
}

// func main() {
//  for i := 21; i < 120; i++ {
//   address := fmt.Sprintf("20.194.168.28:%d", i)
//   conn, err := net.Dial("tcp", address)
//   if err != nil {
//    fmt.Printf("%s failed 关闭了\n", address)
//    continue
//   }
//   conn.Close()
//   fmt.Printf("%s connected 打开了!!!\n", address)
//  }
// }

并发的 TCP 扫描器 - WORKER 池

package main

import (
 "fmt"
 "sync"
)

func worker(ports chan int, wg *sync.WaitGroup) {
 for p := range ports {
  fmt.Println("p", p)
  wg.Done()
 }
}

func main() {
 ports := make(chan int, 100)
 var wg sync.WaitGroup

 for i := 0; i < cap(ports); i++ {
  go worker(ports, &wg)
 }

 for i := 1; i < 1024; i++ {
  wg.Add(1)
  ports <- i
 }

 wg.Wait()
 close(ports)
}

// func main() {
//  start := time.Now()
//  var wg sync.WaitGroup
//  for i := 21; i < 120; i++ {
//   wg.Add(1)
//   go func(j int) {
//    defer wg.Done()
//    address := fmt.Sprintf("20.194.168.28:%d", j)
//    conn, err := net.Dial("tcp", address)
//    if err != nil {
//     fmt.Printf("%s 关闭了\n", address)
//     return
//    }
//    conn.Close()
//    fmt.Printf("%s 打开了!!!\n", address)
//   }(i)
//  }
//  wg.Wait()
//  elapsed := time.Since(start) / 1e9
//  fmt.Printf("\n\n%d seconds", elapsed)
// }

// func main() {
//  for i := 21; i < 120; i++ {
//   address := fmt.Sprintf("20.194.168.28:%d", i)
//   conn, err := net.Dial("tcp", address)
//   if err != nil {
//    fmt.Printf("%s failed 关闭了\n", address)
//    continue
//   }
//   conn.Close()
//   fmt.Printf("%s connected 打开了!!!\n", address)
//  }
// }

优化之后

package main

import (
 "fmt"
 "net"
 "sort"
)

func worker(ports chan int, results chan int) {
 for p := range ports {
  address := fmt.Sprintf("20.194.168.28:%d", p)
  conn, err := net.Dial("tcp", address)
  if err != nil {
   results <- 0
   continue
  }
  conn.Close()
  results <- p
 }
}

func main() {
 ports := make(chan int, 100)
 results := make(chan int)
 var openports []int
 var closeports []int

 for i := 0; i < cap(ports); i++ {
  go worker(ports, results)
 }
 go func() {
  for i := 1; i < 1024; i++ {

   ports <- i
  }
 }()

 for i := 1; i < 1024; i++ {
  port := <-results
  if port != 0 {
   openports = append(openports, port)
  } else {
   closeports = append(closeports, port)
  }
 }

 close(ports)
 close(results)

 sort.Ints(openports)
 sort.Ints(closeports)

 for _, port := range closeports {
  fmt.Printf("%d closed\n", port)
 }

 for _, port := range openports {
  fmt.Printf("%d opened\n", port)
 }
}

// func main() {
//  start := time.Now()
//  var wg sync.WaitGroup
//  for i := 21; i < 120; i++ {
//   wg.Add(1)
//   go func(j int) {
//    defer wg.Done()
//    address := fmt.Sprintf("20.194.168.28:%d", j)
//    conn, err := net.Dial("tcp", address)
//    if err != nil {
//     fmt.Printf("%s 关闭了\n", address)
//     return
//    }
//    conn.Close()
//    fmt.Printf("%s 打开了!!!\n", address)
//   }(i)
//  }
//  wg.Wait()
//  elapsed := time.Since(start) / 1e9
//  fmt.Printf("\n\n%d seconds", elapsed)
// }

// func main() {
//  for i := 21; i < 120; i++ {
//   address := fmt.Sprintf("20.194.168.28:%d", i)
//   conn, err := net.Dial("tcp", address)
//   if err != nil {
//    fmt.Printf("%s failed 关闭了\n", address)
//    continue
//   }
//   conn.Close()
//   fmt.Printf("%s connected 打开了!!!\n", address)
//  }
// }

总结

通过这篇文章,我们从 TCP 的基础知识出发,用 Go 语言逐步构建了一个功能强大的 TCP 扫描器。从简单的顺序扫描,到 goroutine 的并发加速,再到 worker 池的优化设计,Go 的并发原语(如 goroutine 和 channel)让网络编程变得高效而优雅。无论你是想深入理解 TCP 协议,还是提升 Go 编程能力,这个扫描器的实现过程都为你提供了宝贵的实践经验。现在,试着运行代码,或者挑战自己扫描其他服务器吧!用 Go 探索网络的奥秘,你准备好了吗?

  • 原创
  • 学分: 1
  • 分类: Go
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
该文章收录于 Go 语言
4 订阅 24 篇文章

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0x89EE...a439
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。