常用的Go工具命令
go run
该命令将编译并执行main包
例如执行命令go run main.go 就会编译并执行main.go中的代码
go build
go run虽然编译并执行了代码但并未生成独立的二进制文件. 使用go build 命令将会编译(包括所有的包及其依赖项)并生成一个二进制文件
使用以下命令可以去除生成二进制文件中包含调试信息和字符表,以减小文件的大小
>go build -ldflags "-w -s"
在运行build命令时可以设置GOOS和GOARCH来实现交叉编译
>GOOS="linux" GOARCH="amd64" go build main.go
使用以上命令将生成64位ELF文件
go doc
使用该命令可以查询有关包,函数,方法或变量的文档
如下为查询函数fmt.Println()的详细信息
>go doc fmt.Println
package fmt // import "fmt"
func Println(a ...any) (n int, err error)
Println formats using the default formats for its operands and writes to
standard output. Spaces are always added between operands and a newline
is appended. It returns the number of bytes written and any write error
encountered.
go get
使用go get命令来下载所需要的第三方包,下载后实际的包将会存放在$GOPATH/src目录中
go fmt
go fmt命令可以自动格式化源码,它将会使用正确的换行符,缩进和大括号对齐来设置代码样式
TCP扫描
判断端口是否开放方法如下首先客户端发送个syn数据包,该数据包表示通信开始,然后服务器以syn/ack进行响应提示客户端以ack作为结束,之 后就可以进行数据传输。如果端口关闭则服务器会以个rst数据包而不是syn/ack进 行响应。如果流量被防火墙过滤’则客户端通常不会从服务器收到任何响应.
以下是一个基本的端口扫描,仅扫描scanme.nmap.org的80端口
package main
import (
"fmt"
"net" //用于进行网络连接的包
)
func main() {
_, err := net.Dial("tcp", "scanme.nmap.org:80")
//net.Dial()函数第一个参数是选择网络连接的类型,第二个参数是选择连接的主机它的格式是"host:port"
//net.Dial()函数的返回值是Conn和error,如果连接成功error的值为空
if err == nil {//判断err的值是否为空;如果为空则打印连接成功
fmt.Println("Connection successful")
}
}
下面需要对上面的代码进行改进,实现对多个端口的扫描
package main
import (
"fmt"
"net"
)
func main() {
for i := 1; i <= 1024; i++ {
//使用i代表端口号;循环对1-1024的端口进行连接
address := fmt.Sprintf("scanme.nmap.org:%d", i)
conn, err := net.Dial("tcp", address)
if err != nil {
// 如果连接的端口返回错误信息,则直接继续扫描下一个端口
continue
}
conn.Close() //关闭成功的连接
fmt.Printf("%d open\n", i)//输出开放的端口号
}
}
上面的代码虽然实现了对多个端口的扫描,但速度太慢不能突出go的优势,需要实现多个协程共同执行以增加端口扫描的速度.代码如下:
package main
import (
"fmt"
"net"
"sync" //导入用于同步的包
)
func main() {
var wg sync.WaitGroup//定义结构体WaitGroup防止因为主goroutine结束而其他goroutine没有完成扫描就结束整个程序
for i := 1; i <= 1024; i++ {
wg.Add(1)//每开启一个协程,wg就加一
go func(j int) {
defer wg.Done()//每结束一个协程,wg就减一
address := fmt.Sprintf("scanme.nmap.org:%d", j)
conn, err := net.Dial("tcp", address)
if err != nil {
return
}
conn.Close()
fmt.Printf("%d open\n", j)
}(i)//i是匿名函数传入的参数;相当于将i赋值给j
}
wg.Wait()//只有当wg的值为0时意味着所有goroutine执行完毕,才能退出程序,否则进行等待
}
使用以上的代码虽然能够加快扫描的速度但是还是存在问题,当需要扫描65535个端口时则会开启65535个协程会造成扫描的结果不准确.所以需要对代码进行改进来限制协程的数量以保证结果的准确性,代码如下:
package main
import (
"fmt"
"net"
"sort"
)
func worker(ports, results chan int) {
for p := range ports {
address := fmt.Sprintf("scanme.nmap.org:%d", p)
conn, err := net.Dial("tcp", address)
if err != nil {
results <- 0 //如果端口连接失败,向result通道传入0
continue
}
conn.Close()
results <- p //端口连接成功,向result通道传入端口号
}
}
func main() {
ports := make(chan int, 100) //创建一个名为ports容量为100的通道
results := make(chan int)
var openports []int //创建一个切片用于存放开放的端口
for i := 0; i < cap(ports); i++ {
go worker(ports, results) //循环启动100个协程开始进行端口扫描;100个协程从ports中获取要扫描的端口号;将扫描的结果存入results通道内
}
go func() { //另启动一个协程,将要扫描的端口号传入ports中
for i := 1; i <= 1024; i++ {
ports <- i
}
}()
for i := 0; i < 1024; i++ {
port := <-results //循环获取result通道中的结果1024次;因为当result中没有数据时会发生阻塞,所以会保证接收到1024个结果,而不会出现主程序执行完毕,而其他协程没有执行完的现象,所以不需要WaitGroup结构体
if port != 0 {
openports = append(openports, port) //将开放的端口存放到openports切片中
}
}
close(ports) //关闭通道
close(results)
sort.Ints(openports) //将开放的端口按顺序排序
for _, port := range openports {
fmt.Printf("%d open\n", port) //将开放的端口顺序打印输出
}
}
注:该代码会将所有端口扫描完毕之后统一输出而不是扫描到结果后立即输出所以会让人感觉不如上面的代码运行快,当然,可以尝试调大worker的数量以增加扫描的速度,但worker数量太大可能会降低扫描的准确性.