2016-07-17 129 views
1

我目前正在学习去的过程。要做到这一点,我正在制作一个相对简单的portscanner。在goroutines扫描端口

我面临的问题是扫描这些端口需要相当长的时间。我的行为是,如果我扫描端口(定义为int32的数组(protobuf doesnt支持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"` 
} 

如在代码snipp的第一行看到et,我已经定义了一个分片,以保存所有结果,我的想法是我的应用程序并行扫描端口,并在完成后将结果发送给感兴趣的任何人。

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

我跑这个基准来测试其性能:

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

的原因是什么它卡住。看看这段代码是否给了我们什么东西?因此,在短期

,我想我的最终结果是为每个端口在不同的走常规扫描,当他们全部结束,一切都一起在一个产生ScanResult的切片。

我希望我已经清楚,并为您提供足够的信息来帮助我。

哦,IM尤其是寻找指针,学习一下,不persee工作代码示例。

回答

2

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

除此之外,你的代码看起来很好。

+0

你的先生,是个英雄!我根本不知道你需要关闭频道。现在有道理。感谢您的解释。端口扫描的性能现在好多了。 –

2

由于@creker写道,你必须关闭通道,othervise其读取这将是无限循环的循环。不过,我不同意,wg.Wait()后,只是增加close(ch)是走正道 - 这将意味着从通道读取值的循环将无法启动,直到所有的端口扫描(所有connect()调用返回)。我想说你想尽快开始处理结果。对于你,使生产者和消费者是不同的够程,像下面

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或频道调整你的代码,所以它更可重用。如果事实证明生产者产生的数据比消费者读取数据更快,你也可以使用缓冲通道,但你可能不需要缓冲区为len(splitPorts),但是更小。

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

+2

不要在闭包中直接使用循环变量'port'。当goroutine收到相同的值时,你会遇到一个错误。 – creker

+1

很好的接收,改变了代码。整件事是在浏览器writen,无需编译它,它只是为了演示的主要思想... – ain

+0

@ain谢谢你的伟大的答案。我已经接受了克雷克的回答,因为它确实回答并解决了我的问题,所以我很抱歉。但对你的解决方案赞不绝口。非常明确,总体来说还有不错的改进 –