手把手教你用Go语言打造一款简易TCP端口扫描器

mb6066e504cce6f · · 636 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

Hey,大家好呀,我是码农,星期八。

这次呢, 咱们来实现一个简单的TCP端口扫描器!

也来体验一下***的风采!

TCP扫描本质

我们在使用TCP进行连接时,需要知道对方机器ip:port

正常握手

连接成功的话,流程如下。

图片

连接失败

有正常,就有失败,如果被连接方关闭的话,流程如下。

图片

如果有防火墙

还有一种可能是,端口开放,但是防火墙拦截,流程如下。

图片

代码

本质理解之后,就可以开始撸代码了。

在Go中,我们通常使用net.Dial进行TCP连接

它就两种情况

  • 成功:返回conn。

  • 失败:err != nil

普通版

相对来说,刚开始时,我们可能都不是太胆大,都是先写原型,也不考虑性能。

代码

package main
import (    "fmt"    "net")
func main() {    var ip = "192.168.43.34"    for i := 21; i <= 120; i++ {        var address = fmt.Sprintf("%s:%d", ip, i)        conn, err := net.Dial("tcp", address)        if err != nil {            fmt.Println(address, "是关闭的")            continue        }        conn.Close()        fmt.Println(address, "打开")  }}

执行结果

但是这个过程是非常缓慢的。

因为net.Dial如果连接的是未开放的端口,一个端口可能就是20s+,所以,我们为什么学习多线程懂了把!!!

多线程版

上述是通过循环去一个个连接ip:port的,那我们就知道了,在一个个连接的位置,让多个人去干就好了。

所以,多线程如下。

代码

package main
import (    "fmt"    "net"    "sync"    "time")
func main() {
   var begin =time.Now()    //wg    var wg sync.WaitGroup    //ip    var ip = "192.168.99.112"    //var ip = "192.168.43.34"    //循环    for j := 21; j <= 65535; j++ {        //添加wg        wg.Add(1)        go func(i int) {            //释放wg            defer wg.Done()            var address = fmt.Sprintf("%s:%d", ip, i)            //conn, err := net.DialTimeout("tcp", address, time.Second*10)            conn, err := net.Dial("tcp", address)            if err != nil {                //fmt.Println(address, "是关闭的", err)                return            }            conn.Close()            fmt.Println(address, "打开")        }(j)}    //等待wg    wg.Wait()    var elapseTime = time.Now().Sub(begin)    fmt.Println("耗时:", elapseTime)}

执行结果

其实是同时开启了6W多个线程,去扫描每个ip:port

所以耗时最长的线程结束的时间,就是程序结束的时间。

感觉还行,20s+扫描完6w多个端口!!!

线程池版

上面我们简单粗暴的方式为每个ip:port都创建了一个协程。

虽然在Go中,理论上协程开个几十万个都没问题,但是还是有一些压力的。

所以我们应该采用一种相对节约的方式进行精简代码,一般采用线程池方式。


本次使用的线程池包:gohive

地址:https://github.com/loveleshsharma/gohive

简单介绍

代码

package main
//线程池方式import (    "fmt"    "github.com/loveleshsharma/gohive"    "net"    "sync"    "time")
//wgvar wg sync.WaitGroup
//地址管道,100容量var addressChan = make(chan string, 100)
//工人func worker() {    //函数结束释放连接    defer wg.Done()    for {        address, ok := <-addressChan        if !ok {            break        }        //fmt.Println("address:", address)        conn, err := net.Dial("tcp", address)        //conn, err := net.DialTimeout("tcp", address, 10)        if err != nil {            //fmt.Println("close:", address, err)            continue        }        conn.Close()        fmt.Println("open:", address)}}func main() {    var begin = time.Now()    //ip    var ip = "192.168.99.112"    //线程池大小    var pool_size = 70000    var pool = gohive.NewFixedSizePool(pool_size)
   //拼接ip:端口    //启动一个线程,用于生成ip:port,并且存放到地址管道种    go func() {        for port := 1; port <= 65535; port++ {            var address = fmt.Sprintf("%s:%d", ip, port)            //将address添加到地址管道            //fmt.Println("<-:",address)            addressChan <- address        }        //发送完关闭 addressChan 管道        close(addressChan)}()    //启动pool_size工人,处理addressChan种的每个地址    for work := 0; work < pool_size; work++ {        wg.Add(1)        pool.Submit(worker)}    //等待结束    wg.Wait()    //计算时间    var elapseTime = time.Now().Sub(begin)    fmt.Println("耗时:", elapseTime)}

执行结果

图片

我设置的线程池大小是7w个,所以也是一下子开启6w多个协程的,但是我们已经可以进行线程大小约束了。


假设现在有这样的去求,有100个ip,需要扫描每个ip开放的端口,如果采用简单粗暴开线程的方式.

那就是100+65535=6552300,600多w个线程,还是比较消耗内存的,可能系统就会崩溃,如果采用线程池方式。

将线程池控制在50w个,或许情况就会好很多。

但是有一点的是,在Go中,线程池通常需要配合chan使用,可能需要不错的基础。

总结

本篇更偏向于乐趣篇,了解一下好玩的玩意

其实还可以通过net.DialTimeout连接ip:port,这个可以设置超时时间,比如超时5s就判定端口未开放。

此处就不做举例了。

咱们主要使用三种方式来实现功能。

  • 正常版,没有并发,速度很慢。

  • 多协程版,并发,性能很高,但是协程太多可能会崩溃。

  • 协程池版,并发,性能高,协程数量可控。

通常情况下,如果基础可以,更推荐使用协程池方式。

如果在操作过程中有任何问题,记得下面留言,我们看到会第一时间解决问题。

用微笑告诉别人,今天的我比昨天强,今后也一样。

我是码农星期八,如果觉得还不错,记得动手点赞一下哈。

感谢你的观看。




有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:mb6066e504cce6f

查看原文:手把手教你用Go语言打造一款简易TCP端口扫描器

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

636 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传