我目前正处于学习过程中。为此,我正在制作一个相对简单的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工作代码示例。
答案 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
。