Go语言TCP端口扫描器主协程无法接收通道最后值求助
Go TCP端口扫描器主协程阻塞问题分析与修复
问题现象
实现的两个Go TCP端口扫描器(自行编写/书籍示例)均存在同一问题:主协程无法接收results通道的最后一个值,陷入无限等待;扫描端口数少于21时程序正常,超过1000时未被接收的结果增至约10个。
问题代码(书籍示例)
func worker(ports, results chan int) { for p := range ports { address := fmt.Sprintf("scanme.nmap.org:%d", p) conn, err := net.Dial("tcp", address) if err != nil { results <- 0 fmt.Println("sent", p) continue } conn.Close() results <- p fmt.Println("sent", p) } } func main() { ports := make(chan int, 100) results := make(chan int) var openports []int for i := 0; i < cap(ports); i++ { go worker(ports, results) } go func() { for i := 1; i <= 50; i++ { ports <- i } }() for i := 0; i < 50; i++ { port := <-results // 卡在第50次接收,无限等待 fmt.Println("received", port, i) if port != 0 { openports = append(openports, port) } } close(ports) close(results) sort.Ints(openports) fmt.Println(openports) }
问题根源
net.Dial无超时机制:默认的net.Dial没有设置超时时间,当扫描某些被过滤或无响应的端口时,连接会无限阻塞,对应的worker协程无法执行到发送results的步骤,导致主协程一直等待该结果。ports通道未及时关闭:发送端口的匿名协程完成发送后未关闭ports,导致worker协程处理完所有端口后仍会阻塞在range ports上,造成资源泄漏,但这不是主协程阻塞的直接原因。results为无缓冲通道:worker发送结果时必须等待主协程接收,放大了net.Dial阻塞带来的影响,但并非核心问题。
修复方案
1. 给连接添加超时时间
使用net.DialTimeout替代net.Dial,避免连接无限阻塞:
import "time" func worker(ports, results chan int) { timeout := 5 * time.Second // 设置5秒超时,可根据需求调整 for p := range ports { address := fmt.Sprintf("scanme.nmap.org:%d", p) conn, err := net.DialTimeout("tcp", address, timeout) if err != nil { results <- 0 fmt.Println("sent", p) continue } conn.Close() results <- p fmt.Println("sent", p) } }
2. 发送完端口后关闭ports通道
修改匿名协程,发送完所有端口后关闭ports,让worker协程处理完任务后正常退出:
go func() { for i := 1; i <= 50; i++ { ports <- i } close(ports) // 发送完成后关闭通道 }()
3. 可选:给results通道添加缓冲
将results改为带缓冲的通道,减少worker发送结果时的阻塞:
results := make(chan int, 50) // 缓冲容量与端口数一致
修复后效果
修改后,所有worker协程都会在超时时间内完成端口扫描并发送结果,主协程能顺利接收全部50个结果,不会陷入无限等待;同时ports通道被及时关闭,避免了资源泄漏。
内容的提问来源于stack exchange,提问作者Tsolan
相关产品推荐
相关产品推荐

