分析某款go端口扫描器之一

一、概述

进来在学go的端口检测部分,但是自己写遇到很多问题,又不知道从何入手,故找来网上佬们写的现成工具,学习一波怎么实现的。分析过程杂乱,没啥思路,勿喷。

项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go

二、目录结构分析

总体来说,这个工具主要三部分,cmd(主程序)、core(核心框架部分)、util(工具部分),后续的分析也从这三个部分开始讲解

三、util目录

此目录下主要有三个文件,分别为file.go、log.go、shuffle.go,以下逐一分析

1、file.go(逐行读取文件内容)

func GetLines(filename string) (out []string, err error)

主要内容为一个GetLines方法,其接收一个文件名作为参数,并返回文件中非空行的内容组成字符串切片和可能的错误,主要作用是逐行读取文件,并将非空行的内容添加到"out"切片中。

Go 复制代码
func GetLines(filename string) (out []string, err error) {
	if filename == "" {//先判断文件名是否为空,为空则提示错误
		return out, errors.New("no filename")
	}
	file, err := os.Open(filename)//打开文件
	if err != nil {
		return out, err
	}

	defer file.Close()//读取完记得关闭
	scanner := bufio.NewScanner(file)//读取文件的内容
	scanner.Split(bufio.ScanLines)//一行一行读取分隔

	for scanner.Scan() {//逐行读取并将文本内容追加到out切片中
		line := strings.TrimSpace(scanner.Text())
		if line != "" {
			out = append(out, line)
		}
	}
	return
}

2、log.go(日志记录)

func NewLogger(filename string, std bool) *log.Logger

主要内容为一个 NewLogger方法,它根据提供的参数创建一个新的日志记录器对象。该函数接受一个文件名和一个布尔值参数。

  • filename 参数用于指定日志输出的文件名,如果为空字符串则表示日志将输出到标准输出(stdout)。
  • std 参数是一个布尔值,如果设置为 true,则日志会同时输出到文件和标准输出;如果设置为 false,则只输出到文件。

这个函数的目的是根据参数创建一个日志记录器,可以指定输出到文件还是标准输出,并可以选择是否同时输出到文件和标准输出。

Go 复制代码
func NewLogger(filename string, std bool) *log.Logger {
	var out io.Writer
	if filename == "" {
		out = os.Stdout //如果传入的filename为空,将out设置为标准输出
	} else {//如果不为空,则打开这个文件,
		outFile, _ := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if std {//如果 std为true,则将输出同时定向到标准输出和文件中,通过 io.MultiWriter 将os.Stdout 和打开的文件合并成一个多写入器
			out = io.MultiWriter(os.Stdout, outFile)
		} else {//如果 std 参数为 false,则直接将输出定向到打开的文件中
			out = outFile
		}
	}
	logger := log.New(out, "", 0)//使用 log.New 方法创建一个新的日志记录器对象 logger,将输出对象 out 作为日志记录器的输出,设置空的前缀,并且不添加任何额外的选项(flag)
	return logger
}

3、shuffle.go

主要包含3个方法:NewShuffle、Get、IsUint16InList,以及一个结构体Shuffle

type Shuffle struct

Go 复制代码
type Shuffle struct {
	rl   []uint16 // 乱序序列,存储的是一般轮次的乱序序列
	rl2  []uint16 // 最后一轮乱序序列(无法整除时使用)
	n    uint16   // 乱序精度,用来限制乱序序列的长度
	size uint64    //乱序序列的大小
}

func NewShuffle(size uint64) *Shuffle

函数接收一个 size 参数作为生成乱序序列的大小。

Go 复制代码
// NewShuffle 局部乱序
func NewShuffle(size uint64) *Shuffle {
	if size == 0 { //如果size为0, 返回nil
		return nil
	}
	sf := &Shuffle{size: size}// 创建一个新的 Shuffle 结构体,设置其 size 字段为传入的值
	if size > 100 {
		sf.n = 100 //如果size>100,设置乱序精度为100
	} else {
		sf.n = uint16(size)//否则设置乱序精度为size 的 uint16 类型。
	}

    //通过循环填充 rl 切片,创建一般轮次的乱序序列。
    //使用 rand 包生成随机数种子,对 rl 进行乱序化操作。
    //如果 size 无法整除 n,则设置 rl2 切片,并生成最后一轮乱序序列
	// 通用轮次
	sf.rl = make([]uint16, sf.n)
	for i := uint16(0); i < sf.n; i++ {
		sf.rl[i] = i
	}
	// 洗牌方法
	r := rand.New(rand.NewSource(int64(size)))
	r.Shuffle(int(sf.n), func(i, j int) {
		sf.rl[i], sf.rl[j] = sf.rl[j], sf.rl[i]
	})
	// 最后一轮无法整除时新建对应长度的rl2
	t := uint16(size % uint64(sf.n))
	if t != 0 {
		sf.rl2 = make([]uint16, t)
		for i := uint16(0); i < t; i++ {
			sf.rl2[i] = i
		}
		r.Shuffle(int(t), func(i, j int) {
			sf.rl2[i], sf.rl2[j] = sf.rl2[j], sf.rl2[i]
		})
	}
	return sf
}

func (sf *Shuffle) Get(index uint64) uint6

Get 方法接收一个索引 index,用于获取转换后的索引值。首先计算 t 为 index 对 sf.n 取模得到的结果。然后根据索引 index 与 n 的关系,决定使用哪个乱序序列。如果无法整除,则使用 rl2,否则使用 rl

Go 复制代码
// Get 根据索引获取转换后的索引值
func (sf *Shuffle) Get(index uint64) uint64 {
	t := index % uint64(sf.n)
	// 最后一轮无法整除时用rl2
	if index-t+uint64(sf.n) > sf.size {
		return index - t + uint64(sf.rl2[uint16(t)])
	}
	return index - t + uint64(sf.rl[uint16(t)])
}

func IsUint16InList(code uint16, list []uint16) bool

IsUint16InList 函数接收一个 code 和一个 list,用于判断 list 中是否存在 code。

它遍历 list 切片,如果发现存在与 code 相等的元素,则返回 true;如果遍历完 list 后都没有找到,则返回 false。

Go 复制代码
func IsUint16InList(code uint16, list []uint16) bool {
	for _, e := range list {
		if e == code {
			return true
		}
	}
	return false
}
相关推荐
用户698608395572 分钟前
用go从零构建写一个RPC(2)--连接池与长连接引入
后端·rpc
CodeSaku2 分钟前
从SpringTask开始入手定时任务
后端
GetcharZp9 分钟前
Go语言实现屏幕截取+实时推流
后端·音视频开发
天天摸鱼的java工程师16 分钟前
SpringBoot 实战:轻松实现接口数据脱敏
java·后端
GoGeekBaird37 分钟前
69天探索操作系统-第65天:为内核操作构建可扩展和线程安全的内存分配器
后端·操作系统
黄暄1 小时前
分布式锁优化:使用Lua脚本保证释放锁的原子性问题
java·redis·分布式·后端·junit·lua
Rocky4011 小时前
SpringMVC的注解
spring boot·后端·mvc
寻月隐君1 小时前
快速上手 Web3:用 @solana/kit 在 Solana 上创建钱包并查询余额
后端·web3·github
独立开阀者_FwtCoder1 小时前
尤雨溪宣布:Vue 3.6 重大更新!Vite 将彻底重写!
前端·javascript·后端
腾云@1 小时前
virtio介绍 (三)--spdk作为virtio后端处理nvme盘io的流程--上
linux·运维·后端·云计算