扫描goroutines中的端口

时间:2016-07-17 14:59:48

标签: go goroutine

我目前正处于学习过程中。为此,我正在制作一个相对简单的portscanner。

我面临的问题是扫描这些端口需要相当长的时间。我所拥有的行为是,如果我扫描端口(定义为int32的数组(protobuf不支持int16),不使用goroutines工作,但是当扫描超过5个端口时,你可以想象,因为它不是很慢并行运行。

为了实现并行性,我提出了以下代码(解释+问题出现在代码之后):

//entry point for port scanning
var results []*portscan.ScanResult
//len(splitPorts) is the given string (see benchmark below) chopped up in an int32 slice
ch := make(chan *portscan.ScanResult, len(splitPorts))

var wg sync.WaitGroup
for _, port := range splitPorts {
    connect(ip, port, req.Timeout, ch, &wg)
}
wg.Wait()

for elem := range ch {
    results = append(results, elem)
}

// go routine
func connect(ip string, port, timeout int32, ch chan *portscan.ScanResult, wg *sync.WaitGroup) {
    wg.Add(1)
    go func() {
        res := &portscan.ScanResult{
            Port:   port,
            IsOpen: false,
        }
        conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), time.Duration(timeout)*time.Millisecond)

        if err == nil {
            conn.Close()
            res.IsOpen = true
        }
        ch <- res
        wg.Done()
    }()
}

所以protobuf为我准备了一个结构,如下所示:

type ScanResult struct {
    Port   int32 `protobuf:"varint,1,opt,name=port" json:"port,omitempty"`
    IsOpen bool  `protobuf:"varint,2,opt,name=isOpen" json:"isOpen,omitempty"`
}

如代码片段的第一行所示,我已经定义了一个切片,以保存所有结果,我的想法是我的应用程序并行扫描端口,完成后,将结果发送给感兴趣的人。< / p>

但是,使用此代码,程序会卡住。

我运行此基准来测试其性能:

func BenchmarkPortScan(b *testing.B) {
  request := &portscan.ScanPortsRequest{
      Ip:        "62.129.139.214",
      PortRange: "20,21,22,23",
      Timeout:   500,
  }

  svc := newService()

  for i := 0; i < b.N; i++ {
      svc.ScanPorts(nil, request)
  }
}

它被卡住的原因是什么?看这段代码会有什么用处吗?

简而言之,我希望我的最终结果是每个端口都在不同的go例程中进行扫描,当它们全部完成时,所有内容都会在ScanResult的结果中汇集在一起​​。

我希望我一直很清楚并为你提供足够的信息来帮助我。

哦,我特别在寻找指针和学习位,而不是persee工作代码示例。

2 个答案:

答案 0 :(得分:2)

您需要在wg.Wait()之后关闭频道。否则你的循环范围会被卡住。

除此之外,您的代码看起来还不错。

答案 1 :(得分:2)

正如@creker所写,你必须关闭通道,否则从它读取的循环将是无限循环。但是,我不同意在close(ch)之后添加wg.Wait()是正确的方法 - 这意味着在扫描所有端口之前,从通道读取值的循环不会启动(所有connect()次呼叫都返回)。我想你想在结果可用时立即开始处理结果。为此,你必须重新构建你的代码,以便生产者和消费者是不同的goroutines,如下面的

var results []*portscan.ScanResult
ch := make(chan *portscan.ScanResult)

// launch the producer goroutine    
go func() {
   var wg sync.WaitGroup
   wg.Add(len(splitPorts))
   for _, port := range splitPorts {
       go func(port int32) {
          defer wg.Done()
          ch <- connect(ip, port, req.Timeout)
       }(port)
   }
   wg.Wait()
   close(ch)
}()

// consume results
for elem := range ch {
    results = append(results, elem)
}

func connect(ip string, port, timeout int32) *portscan.ScanResult {
    res := &portscan.ScanResult{
            Port:   port,
            IsOpen: false,
    }
    conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), time.Duration(timeout)*time.Millisecond)

    if err == nil {
        conn.Close()
        res.IsOpen = true
    }
    return res
}

注意,现在通道是无缓冲的,connect函数不知道waitgroup或channel,所以它更可重用。您也可以使用缓冲通道,如果事实证明生产者生成数据的速度比消费者读取的速度快,但您可能不需要缓冲区len(splitPorts)但更小。

另一个优化可能是预先分配results数组,因为您似乎事先知道结果的数量(len(splitPorts)),因此您不需要使用append