使用Golang开发端口扫描工具可以充分利用其原生并发特性,实现高效的网络端口探测,同时还能灵活控制扫描逻辑和结果展示形式,适配不同的使用场景。

Golang端口扫描工具核心逻辑
端口扫描的核心原理是向目标主机的指定端口发起TCP连接请求,根据连接是否成功判断端口的开放状态。Golang的net包提供了完善的网络操作接口,能够轻松实现基础的端口探测功能。
基础单端口探测实现
单端口探测是最基础的功能,通过net.DialTimeout方法设置连接超时时间,避免无效等待。以下是单端口探测的示例代码:
package main
import (
"fmt"
"net"
"time"
)
// 探测单个端口是否开放
func scanPort(host string, port int, timeout time.Duration) bool {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return false
}
defer conn.Close()
return true
}
func main() {
host := "127.0.0.1"
port := 8080
timeout := 3 * time.Second
if scanPort(host, port, timeout) {
fmt.Printf("端口 %d 处于开放状态n", port)
} else {
fmt.Printf("端口 %d 处于关闭状态n", port)
}
}
并发扫描优化
逐个扫描端口效率极低,Golang的goroutine和channel可以很方便地实现并发扫描,大幅提升扫描速度。需要注意控制并发数量,避免占用过多系统资源。
以下是并发扫描的实现示例,使用带缓冲的channel控制并发上限:
package main
import (
"fmt"
"net"
"sync"
"time"
)
// 并发扫描端口
func concurrentScan(host string, startPort, endPort int, timeout time.Duration, maxConcurrent int) []int {
var openPorts []int
var mu sync.Mutex
var wg sync.WaitGroup
// 控制并发数量的缓冲channel
semaphore := make(chan struct{}, maxConcurrent)
for port := startPort; port <= endPort; port++ {
wg.Add(1)
go func(p int) {
defer wg.Done()
// 获取并发许可
semaphore <- struct{}{}
defer func() { <-semaphore }()
address := fmt.Sprintf("%s:%d", host, p)
conn, err := net.DialTimeout("tcp", address, timeout)
if err == nil {
mu.Lock()
openPorts = append(openPorts, p)
mu.Unlock()
conn.Close()
}
}(port)
}
wg.Wait()
return openPorts
}
func main() {
host := "127.0.0.1"
startPort := 1
endPort := 100
timeout := 2 * time.Second
maxConcurrent := 50
openPorts := concurrentScan(host, startPort, endPort, timeout, maxConcurrent)
fmt.Printf("目标主机 %s 开放的端口有:%vn", host, openPorts)
}
扫描结果展示方案
扫描结果的展示需要根据使用场景选择合适的格式,常见的展示方式包括终端文本展示、结构化数据输出、可视化展示等。
终端格式化展示
直接在终端输出结果时,可以按照端口状态分类展示,同时标注端口对应的常见服务,提升结果的可读性。以下是终端格式化展示的示例:
package main
import (
"fmt"
"net"
"sync"
"time"
)
// 端口服务映射表
var portServiceMap = map[int]string{
21: "FTP",
22: "SSH",
23: "Telnet",
80: "HTTP",
443: "HTTPS",
3306: "MySQL",
6379: "Redis",
8080: "HTTP-Alt",
}
func scanWithService(host string, port int, timeout time.Duration) (int, string, bool) {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return port, "", false
}
defer conn.Close()
service, ok := portServiceMap[port]
if !ok {
service = "未知服务"
}
return port, service, true
}
func main() {
host := "127.0.0.1"
ports := []int{22, 80, 3306, 6379, 8080}
timeout := 2 * time.Second
var wg sync.WaitGroup
results := make([]struct {
Port int
Service string
Open bool
}, len(ports))
for i, port := range ports {
wg.Add(1)
go func(idx, p int) {
defer wg.Done()
port, service, open := scanWithService(host, p, timeout)
results[idx] = struct {
Port int
Service string
Open bool
}{port, service, open}
}(i, port)
}
wg.Wait()
fmt.Println("端口扫描结果:")
fmt.Println("--------------------------------")
for _, res := range results {
if res.Open {
fmt.Printf("端口 %d [%s]:开放n", res.Port, res.Service)
} else {
fmt.Printf("端口 %d [%s]:关闭n", res.Port, res.Service)
}
}
}
结构化数据输出
如果需要将扫描结果存储或对接其他系统,可以输出JSON格式的结构化数据,方便后续处理。以下是输出JSON结果的示例:
package main
import (
"encoding/json"
"fmt"
"net"
"sync"
"time"
)
type PortResult struct {
Host string `json:"host"`
Port int `json:"port"`
Open bool `json:"open"`
Service string `json:"service,omitempty"`
}
func main() {
host := "127.0.0.1"
ports := []int{80, 443, 8080}
timeout := 2 * time.Second
var wg sync.WaitGroup
var mu sync.Mutex
var results []PortResult
portServiceMap := map[int]string{
80: "HTTP",
443: "HTTPS",
8080: "HTTP-Alt",
}
for _, port := range ports {
wg.Add(1)
go func(p int) {
defer wg.Done()
address := fmt.Sprintf("%s:%d", host, p)
conn, err := net.DialTimeout("tcp", address, timeout)
res := PortResult{
Host: host,
Port: p,
}
if err == nil {
res.Open = true
if service, ok := portServiceMap[p]; ok {
res.Service = service
}
conn.Close()
}
mu.Lock()
results = append(results, res)
mu.Unlock()
}(port)
}
wg.Wait()
jsonData, err := json.MarshalIndent(results, "", " ")
if err != nil {
fmt.Println("JSON序列化失败:", err)
return
}
fmt.Println(string(jsonData))
}
开发注意事项
- 超时时间设置要合理,过短会导致误判,过长会影响扫描效率,一般建议设置为1到3秒。
- 并发数量需要控制,过高的并发可能会被目标主机识别为攻击,同时也会占用大量本地资源。
- 扫描行为需要遵守相关网络法规,不要对未授权的目标主机进行扫描。
- 可以添加重试机制,避免因为网络波动导致的误判,提升扫描结果的准确性。