在这种情况下如何正确循环缓冲通道?

时间:2015-07-22 19:52:19

标签: go concurrency channel

我正在尝试使用stdlib制作某种端口扫描程序。这不仅仅是一种练习,所以请不要对所涉及的逻辑发表评论。

查看以下代码:

package main

import (
    "flag"
    "fmt"
    "net"
    "time"
    "strings"
    "strconv"
    "log"
    "sync"
)

var commonPorts = map[int]string {
    21: "ftp",
    22: "sftp",
    80: "http",
    110: "pop3",
    143: "imap",
    443: "https",
    631: "ipp",
    993: "imaps",
    995: "pop3s",
}

type OP struct {
    mu sync.Mutex
    ports []string
}

func (o *OP) SafeAdd(port string) {
    o.mu.Lock()
    defer o.mu.Unlock()
    o.ports = append(o.ports, port)
}


func worker(host string, port int) string {
    address := fmt.Sprintf("%s:%d", host, port)

    conn, err := net.DialTimeout("tcp", address, time.Second * 3)
    if err != nil {
        return ""; // is offline, cannot connect
    }
    conn.Close()

    stringI := strconv.Itoa(port)
    if name, ok := commonPorts[port]; ok {
        stringI += fmt.Sprintf("(%s)", name)
    }

    return stringI
}

func processWithChannels(host string) <-chan string{
    openPort := make(chan string, 1000)
    var wg sync.WaitGroup
    for i := 1; i <= 65535; i++ {
        wg.Add(1)
        go func(openPort chan string, host string, i int) {
            defer wg.Done()
            port := worker(host, i)
            if port != "" {
                openPort <- port
            }
        }(openPort, host, i)
    }
    wg.Wait()
    close(openPort)
    return openPort
}

func main() {
    var host = flag.String("host", "127.0.0.1", "please insert the host")
    var pType = flag.Int("type", 2, "please insert the type")

    flag.Parse()
    fmt.Printf("Scanning: %s...\n", *host)

    if _, err := net.LookupHost(*host); err != nil {
        log.Fatal(err)
    }

    openPorts := &OP{ports: []string{}};

    if *pType == 1 {

        ports := processWithChannels(*host);
        for port := range ports {
            openPorts.SafeAdd(port)
        }

    } else {

        var wg sync.WaitGroup
        for i := 1; i <= 65535; i++ {
            wg.Add(1)
            go func(o *OP, host string, i int){
                defer wg.Done()
                if port := worker(host, i); port != "" {
                    o.SafeAdd(port)
                }
            }(openPorts, *host, i)
        }
        wg.Wait()

    }

    if len(openPorts.ports) > 0 {
        fmt.Printf("Following ports are opened: %s\n", strings.Join(openPorts.ports, ", "))
    } else {
        fmt.Printf("No open port on the host: %s!\n", *host)
    }
}

有两种方法可以通过使用缓冲通道或使用sync.GroupWait开始扫描,并在完成所有扫描后进行保释。

在我看来,在这种情况下,使用sync.GroupWait比使用缓冲通道更有意义并循环通过它直到它为空。但是,在这里使用缓冲通道,除了使用另一个sync.WaitGroup块之外,我还没有看到一种方法来检测通道上没有其他内容以及我应该从for循环中解救出来。

我认为我的问题是,如果我只想使用缓冲通道解决方案,我该如何正确实现它以便我知道何时完成处理以便我可以继续执行其余的代码? (请不要建议超时)。

如果有兴趣的话,这里也是两种类型的小基准:

MacBook-Pro:PortScanner c$ time ./PortScanner -host yahoo.com -type 1
Scanning: yahoo.com...
Following ports are opened: 80(http), 143(imap), 110(pop3), 995(pop3s), 993(imaps)

real    0m4.620s
user    0m1.193s
sys     0m1.284s
MacBook-Pro:PortScanner c$ time ./PortScanner -host yahoo.com -type 2
Scanning: yahoo.com...
Following ports are opened: 110(pop3), 80(http), 143(imap), 995(pop3s), 993(imaps)

real    0m4.055s
user    0m1.051s
sys     0m0.946s

1 个答案:

答案 0 :(得分:2)

如果您需要将超过1000个项目放入频道,则对processWithChannels的呼叫将会挂起。如果您要使用缓冲通道来保存所有值直到处理,则必须有足够的容量来接受所有值。

如果您要将所有值收集到一个切片中,那么就没有理由使用通道,而您的第二个解决方案就可以了。

如果您希望尽快“回传”端口,那么您需要在两种解决方案之间提供一些内容

ports := make(chan string)

var wg sync.WaitGroup
for i := 1; i <= 65535; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        if port := worker(*host, i); port != "" {
            ports <- port
        }
    }(i)
}

go func() {
    wg.Wait()
    close(ports)
}()

for port := range ports {
    fmt.Println("PORT:", port)
}

然而,这可能会遇到问题,例如当您同时拨打所有65535端口时丢失开放端口。以下是使用工作池同时拨号的一种可能模式:

ports := make(chan string)
toScan := make(chan int)
var wg sync.WaitGroup

// make 100 workers for dialing
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        for p := range toScan {
            ports <- worker(*host, p)
        }
    }()
}

// close our receiving ports channel once all workers are done
go func() {
    wg.Wait()
    close(ports)
}()

// feed the ports to the worker pool
go func() {
    for i := 1; i <= 65535; i++ {
        toScan <- i
    }
    // signal the workers to stop
    close(toScan)
}()

for port := range ports {
    if port != "" {
        fmt.Println("PORT:", port)
    }
}