You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题根源

  1. net.Dial无超时机制:默认的net.Dial没有设置超时时间,当扫描某些被过滤或无响应的端口时,连接会无限阻塞,对应的worker协程无法执行到发送results的步骤,导致主协程一直等待该结果。
  2. ports通道未及时关闭:发送端口的匿名协程完成发送后未关闭ports,导致worker协程处理完所有端口后仍会阻塞在range ports上,造成资源泄漏,但这不是主协程阻塞的直接原因。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:32:01