2016-07-17 8 views
1

私は現在、学習の流れにあります。そのために、私は比較的シンプルなポートスキャナーを作っています。ゴルーチンのスキャンポート

私が直面している問題は、これらのポートをスキャンするのにかなりの時間がかかることです。私が行っている動作は、もし私がポートをスキャンすると(intro32の配列として定義されています(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"` 
} 

コードスニップの最初の行に見られるように私はすべての結果を保持するためにスライスを定義しました。私のアプリケーションはポートを並行してスキャンし、それが完了したら興味のある人に結果を送信します。

ただし、このコードを使用すると、プログラムが停止します。

私はその性能をテストする場合は、このベンチマークを実行します。

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の結果スライスに集まるということです。

私はあなたが私を助けるために十分な情報を提供してくれたことを願っています。

ああ、私は特にポインタを探していて、学習ビットではなく、コードサンプルを実行しています。

答えて

2

wg.Wait()の後にチャンネルを終了する必要があります。さもなければ、あなたのループの範囲が止まってしまいます。

コード以外は正常です。

+0

あなたは、英雄です!チャンネルを閉じる必要があるとは思っていませんでした。今は理にかなっている。説明ありがとう。ポートスキャンのパフォーマンスは今よりもはるかに優れています。 –

2

@crekerが書いたように、あなたはチャンネルを閉じなければなりません、それから読むループを無限ループにする必要があります。しかし、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 
} 

注意とwaitgroupまたはチャネルについて知らないconnect機能を、次のようなものがあるようにあなたのコードを再構築する必要がありますより再利用可能です。プロデューサーが消費者が読むよりも速くデータを生成することが判明した場合は、バッファードチャネルを使用することもできますが、バッファーをlen(splitPorts)にする必要はありません。

resultsアレイをあらかじめ割り当てておくと、結果の数値があらかじめわかっているように(len(splitPorts))、appendを使用する必要はありません。

+2

ループ変数 'port'をクロージャで直接使用しないでください。 goroutineが同じ値を受け取ったときにバグが発生します。 – creker

+1

良いキャッチは、コードを変更しました。全体がブラウザで書かれていますが、それをコンパイルすることなく、主なアイデアを示すためのものです。 – ain

+0

@ainあなたの素晴らしい答えをありがとう。私はすでにCrekerの答えを受け入れました。本当に答えを出して問題を解決したので、謝罪しました。しかし、あなたのソリューションには大きな魅力があります。非常に明確で、全体的に素晴らしい改善です。 –

関連する問題