goroutine
的栈在其生命周期开始时很小,可能只有2KB
,但是它并不固定,可按需增大或减小。虽然我们可以无脑创建很多goroutine
来执行操作,但是如果程序出现意外,goroutine
可能会暴涨占据内存,一切就变得不可控,比如我们通过循环来创建goroutine
,当循环条件满足,创建巨额的goroutine
,严重时系统会崩溃。博主也是通过杨旭老师的TCP端口扫描器中发现了这个问题。
1.循环扫描
不使用goroutine
,直接循环扫描端口。
package main
import (
"fmt"
"net"
"time"
)
func main() {
fmt.Println("TCP端口扫描启动...")
start := time.Now()
for i := 1; i <= 20; i++ {
address := fmt.Sprintf("192.168.0.109:%d", i)
conn, err := net.Dial("tcp", address)
if err != nil {
fmt.Printf(" %s 关闭
", address)
continue
}
conn.Close()
fmt.Printf(" %s 打开
", address)
}
elasped := time.Since(start) / 1e9
fmt.Printf("
经过了%d秒
", elasped)
}
TCP端口扫描启动...
192.168.0.109:1 关闭
192.168.0.109:2 关闭
192.168.0.109:3 关闭
192.168.0.109:4 关闭
192.168.0.109:5 关闭
192.168.0.109:6 关闭
192.168.0.109:7 关闭
192.168.0.109:8 关闭
192.168.0.109:9 关闭
192.168.0.109:10 关闭
192.168.0.109:11 关闭
192.168.0.109:12 关闭
192.168.0.109:13 关闭
192.168.0.109:14 关闭
192.168.0.109:15 关闭
192.168.0.109:16 关闭
192.168.0.109:17 关闭
192.168.0.109:18 关闭
192.168.0.109:19 关闭
192.168.0.109:20 关闭
经过了40秒
2.并发扫描
利用goroutine
并发扫描
package main
import (
"fmt"
"net"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
fmt.Println("TCP端口扫描启动...")
start := time.Now()
for i := 1; i <= 20; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
address := fmt.Sprintf("192.168.0.109:%d", i)
conn, err := net.Dial("tcp", address)
if err != nil {
fmt.Printf(" %s 关闭
", address)
return
}
conn.Close()
fmt.Printf(" %s 打开
", address)
}(i)
}
wg.Wait()
elasped := time.Since(start) / 1e9
fmt.Printf("经过了%d秒", elasped)
}
TCP端口扫描启动...
192.168.0.109:4 关闭
192.168.0.109:6 关闭
192.168.0.109:11 关闭
192.168.0.109:7 关闭
192.168.0.109:8 关闭
192.168.0.109:14 关闭
192.168.0.109:3 关闭
192.168.0.109:20 关闭
192.168.0.109:16 关闭
192.168.0.109:12 关闭
192.168.0.109:1 关闭
192.168.0.109:17 关闭
192.168.0.109:18 关闭
192.168.0.109:13 关闭
192.168.0.109:9 关闭
192.168.0.109:10 关闭
192.168.0.109:5 关闭
192.168.0.109:15 关闭
192.168.0.109:19 关闭
192.168.0.109:2 关闭
经过了2秒
即使代码改成全端口1~65535
,时间也只有21s
package main
import (
"fmt"
"net"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
fmt.Println("TCP端口扫描启动...")
start := time.Now()
for i := 1; i <= 65535; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
address := fmt.Sprintf("192.168.0.109:%d", i)
conn, err := net.Dial("tcp", address)
if err != nil {
fmt.Printf(" %s 关闭
", address)
return
}
conn.Close()
fmt.Printf(" %s 打开
", address)
}(i)
}
wg.Wait()
elasped := time.Since(start) / 1e9
fmt.Printf("
经过了%d秒
", elasped)
}
...
192.168.0.109:39702 关闭
192.168.0.109:37282 关闭
192.168.0.109:39490 关闭
192.168.0.109:39795 关闭
192.168.0.109:39613 关闭
192.168.0.109:39707 关闭
192.168.0.109:39723 关闭
192.168.0.109:39732 关闭
192.168.0.109:39806 关闭
192.168.0.109:39682 关闭
192.168.0.109:39809 关闭
192.168.0.109:39663 关闭
192.168.0.109:40006 关闭
192.168.0.109:39712 关闭
192.168.0.109:39804 关闭
192.168.0.109:39731 关闭
192.168.0.109:39701 关闭
192.168.0.109:39810 关闭
192.168.0.109:39725 关闭
192.168.0.109:39805 关闭
192.168.0.109:39713 关闭
192.168.0.109:39489 关闭
192.168.0.109:39791 关闭
192.168.0.109:39640 关闭
192.168.0.109:39667 关闭
192.168.0.109:39661 关闭
经过了21秒
这里也可以看出使用goroutine
做并发操作,真的很快。
3.goroutine pool(池)
上一节最后这样做,就创建了6w多次goroutine
,如果数值更大,100w
,1个亿
(当然IP
端口数字没这么大),只是做大胆试验:然后就可以在监控内存的悬浮窗看到内存噌噌噌
的往上涨,内存迅速消耗殆尽,直至系统卡死,甚至蓝屏。
为了避免这种情况,我们急需创建类似于线程池的机制,去限定我们创建goroutine
的数量,并复用。创建固定数量的m
个goroutine
,利用channel
的机制,往channel
中传递n
个数据,然后分配给这m
个goroutine
,m<=n。
同样是端口扫描任务,代码改造如下:
package main
import (
"fmt"
"net"
)
func main() {
fmt.Println("TCP端口扫描启动...")
fmt.Println("下列端口状态为打开:")
n := make(chan int, 100)
results := make(chan int, 100)
//创建20000个goroutine
for m := 0; m < 20000; m++ {
go worker(n, results)
}
//channel代表了任务数量
for i := 1; i < 65535; i++ {
n <- i
}
close(n)
//结果
for port := range results {
fmt.Println(port)
}
}
func worker(ports <-chan int, results chan<- int) {
for port := range ports {
address := fmt.Sprintf("192.168.0.109:%d", port)
conn, err := net.Dial("tcp", address)
if err != nil {
// fmt.Printf(" %s 关闭
", address)
continue
}
conn.Close()
// fmt.Printf(" %s 打开
", address)
results <- port
}
}
TCP端口扫描启动...
下列端口状态为打开:
80
81
139
902
443
445
135
912
1433
2383
2179
3306
5357
5040
7680
8080
33060
43094
43095
49672
49664
49667
49666
49670
49665
50272
作者:Garfield
同步更新至个人博客:http://www.randyfield.cn/
本文版权归作者所有,未经许可禁止转载,否则保留追究法律责任的权利,若有需要请联系287572291@qq.com