
点击上方蓝字关注我们
0x01 前言
由于Golang有天生的并发优势,所以一直想写一篇能够体现golang并发优势而又比较基础的文章,于是决定使用Golang实现一个简单的并发tcp端口扫描器。准备写一个系列,从单线程单ip多端口一直到多线程多ip多端口,从0开始一个一个例子进行对比,使读者对golang并发编程能有一个较为清晰的认识。
0x02 单线程tcp扫描器实现
package mainimport ("fmt""net""time")func main() {start := time.Now()for i := 21; i <= 80; i++ {start1 := time.Now()address := fmt.Sprintf("1.1.1.1:%d", i)conn, err := net.Dial("tcp", address)if err != nil {fmt.Printf("%d 端口关闭了!\n", i)fmt.Printf("扫描端口%d,共用了%2f秒\n", i, time.Since(start1).Seconds())continue}conn.Close()fmt.Printf("%d 端口开放了!\n", i)fmt.Printf("扫描端口%d,共用了%2f秒\n", i, time.Since(start1).Seconds())}fmt.Printf("共用了%2f秒", time.Since(start).Seconds())}
这段代码中,首先创建一个for循环来遍历我们想要扫描的端口,然后使用net.Dial函数来对该端口进行tcp连接尝试,如果tcp建立连接失败,则说明该端口不开放。反之则开放了该端口,然后关闭连接。
但是这样的效率是非常的慢的,首先是因为net.Dial函数的问题,该函数使用系统默认的tcp超时时间,而如果如果你是mac用户,你会发现扫描一个不开放的端口需要的时间竟然长达75秒。

那是因为在mac中,tcp连接超时默认时间为75秒,我们可以使用命令sysctl net.inet.tcp来查看本地的tcp配置信息。(windows中的tcp超时时间为21妙)

所以在我们使用macos进行编程的时候,最好将net.Dial()函数替换为net.DialTimeout()函数

该函数的第三个参数可以传入一个timeout值,我们可以利用该函数达到修改tcp超时等待时间的目的。所以在进行tcp连接的时候代码应该使用以下方式:
conn, err := net.DialTimeout("tcp", address, 15*time.Second)
这时候我们再看扫描结果,每个端口只需要等待15秒了。

0x03 并发tcp端口扫描器
刚才使用单线程的扫描器,扫描时间为每个端口扫描时间之和。这样一来没有效率,二来更体现不出golang并发的优势。所以我们需要修改代码,将代码修改成以下格式:
package mainimport ("fmt""net""sync""time")func main() {start := time.Now()var wg sync.WaitGroupfor i := 21; i <= 120; i++ {wg.Add(1)go func(j int) {defer wg.Done()address := fmt.Sprintf("1.1.1.1:%d", j)conn, err := net.DialTimeout("tcp", address, 15*time.Second)if err != nil {fmt.Printf("%d 该端口关闭了!\n", j)return}conn.Close()fmt.Printf("%d 该端口打开了!\n", j)}(i)}wg.Wait()fmt.Printf("共用了%2f秒\n", time.Since(start).Seconds())}
1、我们将刚才建立连接处的代码放入goroutine,然后在循环之中创建goroutine,循环多少次我们就创建多少个goroutine,这样达到并发的效果。
2、使用sync包中的waitgroup来同步等待所有goroutine完成,因为所有goroutine和main函数都是同步工作的,但是main函数结束之后,程序就终止了,那么所有的goroutine也就终止了。所以我们需要使用waitgroup来阻塞main函数等待我们的所有goroutine完成。我们在vscode中按住control+鼠标左键来查看函数的说明:

3、在每一次for循环中,wg都会add,代表增加了一个goroutine。而goroutine运行完毕之后,都会defer一个wg.Done(),代表需要等待的goroutine减少一个。这样main函数之中的wg.Wait()函数就会一直阻塞到所有的goroutine都运行完毕之后。
如果我们不使用sync包中的waitGroup,那么程序没有阻塞等待goroutine的执行就会一下子终止,效果如下:

而正确的效果应该如下:

0x04 总结
至此我们算是简单的完成了并发的tcp扫描器,但是这样并不优雅。比如我们想要扫描1-65535个端口,这样就会开启65535个goroutine,这样会导致我们的资源突然占用率飙升;并且我们现在只能扫描单个ip对应多个端口,如果想要扫描多个端口对应多个ip,请看下篇文章。






