本节课主要利用三个小项目,来巩固go语言的语法并熟悉一些重要的包的使用
01-guessing-game
一个非常简单的猜数字的小游戏,主要用来巩固和熟悉Go语言中的输入输出,和math/read 包的使用
知识点:
- 随机数生成
在Go 1.20以前,使用rand.Seed用于设置全局随机数生成器的种子,以便于每次运行程序时获得不同的随机数序列,但从Go 1.20版本起,默认随机数生成器已不再需要显示调用rand.Seed
- 如果需要一个特定的随机数序列(例如,为了可重复性或测试),可以通过
rand.New和rand.NewSource(seed)创建一个局部随机数生成器,而不是调用全局的rand.Seed。这种方法不会影响到全局的随机数生成器,且更加灵活 - 如果不需要特定的序列,可以直接使用全局的rand包函数,例如
rand.Int()、rand.Float64()等,而无需调用Seed
- bufio包
bufio包是一个Go语言中用于缓冲I/O操作的标准包。他通过将数据缓存到内存来减少系统调用的次数,从而提高文件、网络连接等I/O操作的效率。bufio包特别适用于需要频繁读取或写入小块数据的场景
常用类型:
bufio.Reader 提供了带缓冲的读取功能,适用于从 io.Reader 接口实现的数据源(如文件、网络连接)读取数据。常用方法包括:
ReadByte:读取一个字节。ReadLine:读取一行数据。ReadString:读取直到特定字符的字符串,例如读取到 '\n'。Peek:查看缓冲区的下一个字节,不移动读取位置。
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n') // 读取一行输入直到换行符
fmt.Println("Input:", input)
bufio.Writer 提供了带缓冲的写入功能,可以写入到 io.Writer 接口实现的数据目标(如文件、网络连接)。常用方法包括:
WriteByte:写入一个字节。WriteString:写入字符串。Flush:将缓冲区中的数据写入到底层的输出目标中。
writer := bufio.NewWriter(os.Stdout)
writer.WriteString("Hello, World!") // 写入字符串到缓冲区
writer.Flush() // 强制将缓冲区数据写入输出
bufio.Scanner 是一个简单的逐行或逐词读取文本的工具,非常适合逐行读取文件或输入内容。常用方法包括:
Scan:读取下一个标记(默认是行)。Text:获取最近一次读取到的文本。
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
fmt.Println("Line:", line)
}
- fmt包
fmt 包是 Go 语言标准库中的一个格式化 I/O 包,用于处理字符串格式化和标准输入输出。fmt 包提供了丰富的函数,支持格式化输出、字符串拼接、数据打印等操作,尤其适合处理各种数据类型的格式化输出。
具体内容请见标准文档
02-simpledict
一个调用外部API的小翻译词典,主要用来介绍Go语言中的HTTP服务,json序列化和反序列化
知识点:
- json与Go struct
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
- 序列化与反序列化
- 序列化
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
func main() {
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
}
- 反序列化
// 假设读取到一个[]byte类型的 bodytext
type DictResponse struct {
// ...
// ...
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
- 获取命令行参数
在命令行调用时通过./myapp arg1 arg2 或go run main.go arg1 arg2来传递参数
可以直接使用os.Args来使用参数
03-proxy
实现一个SOCKS5代理服务的基本过程,涉及到net包的使用,利用context包实现goroutine的同步,SOCKS5协议
知识点:
net包
主要提供了函数来创建网络连接,相较于http包面向应用层的HTTP协议,net包主要面向连接层,提供了较为底层的网络服务,主要用于TCP与UDP的实现,具体请见标准文档
context包实现协程同步
...
// 创建一个可以取消的上下文
ctx, cancel := context.WithCancel(context.Background())
// 防御性coding
defer cancel()
// 向服务器转发client的请求
go func() {
_, _ = io.Copy(dest, reader) // Copy是封闭循环 只有当一方关闭连接才退出
cancel()
}()
// 向client递交服务器的response
go func() {
_, _ = io.Copy(conn, dest) // Copy是封闭循环 只有当一方关闭连接才退出
cancel()
}()
<-ctx.Done()
...
调用context.WithCancel(context.Background())生成一个新的context.Context和一个cancel函数
在合适的地方调用<-ctx.Done()来监听取消信号
当调用cancel函数时,ctx.Done()会被触发,收到信号并停止
SOCKS5协议
连接过程
SOCKS5代理建立连接流程包含三个主要阶段:
- 客户端与代理服务器建立连接
- 客户端发送连接请求到SOCKS5代理服务器
- 代理服务器接受客户端的请求后,等待客户端指定认证方法
- 协商认证方法
- 客户端发送支持的认证方法列表,可能包含无验证、用户名/密码验证等
- 代理服务器根据配置选择一种认证方法,返回给客户端。
- 如果需要认证,客户端会发送对应认证信息,如user/passwd
- 如果认证成功,代理服务器会继续连接请求,否则返回错误并中断连接
- 连接目标服务器
- 一旦通过认证,客户端发送请求包括目标IP、端口和请求类型
- 代理服务器根据请求类型建立到目标服务器的连接
- 代理服务器和客户端进行数据传输时,SOCKS5代理仅作为中转,目标服务器对客户端不可见,达到隐藏真实IP的效果
socks5协议的主要请求类型
SOCKS5的支持的请求类型主要有三种:
- CONNECT请求:用于建立TCP连接,适合HTTP、HTTPS等应用协议
- BIND请求:用于被动的TCP连接,一般用于FTP等协议(少见)
- UDP ASSOCIATE请求:用于UDP数据包传输,适合DNS查询,VoIP等
socks5数据包格式
SOCKS5数据包分为两种:协商和请求
- 协商数据包: 用于身份验证流程,包括客户端发起的认证方法列表,以及代理返回的认证方法
客户端的协商数据包:
| 字节偏移量 | 字段名称 | 字节长度 | 说明 |
|---|---|---|---|
| 0 | VER | 1 | 协议版本号(0x05表示SOCKS5) |
| 1 | NMETHODS | 1 | 支持的认证方法数量 |
| 2 | METHODS | 1~255 | 客户端支持的认证方法列表 |
VER:协议版本号,固定为0x05表示SOCKS5
NMETHODS:支持认证方法数量,为后续METHODS字段的长度
METHODS:列表中包括客户端支持的认证方法,每种方法用1字节表示:
- 0x00:不需要认证
- 0x01:GSSAPI认证
- 0x02:用户名密码认证
- 其他值为私有方法
服务器的响应协商数据包:
| 字节偏移量 | 字段名称 | 字节长度 | 说明 |
|---|---|---|---|
| 0 | VER | 1 | 协议版本号(0x05表示SOCKS5) |
| 1 | METHOD | 1 | 服务器选择的认证方法 |
VER:协议版本号,固定为0x05表示SOCKS5
METHOD:服务器选择的认证方法,与客户端提供的方法之一相匹配,如果服务器不支持任何方式,METHOD会返回0xFF,表示认证失败
- 请求数据包: 包括目标的IP地址、端口号、请求类型。根据socks5协议,目标地址可以是IPv4、IPv6或域名
客户端发送的请求数据包:
| 字节偏移量 | 字段名称 | 字节长度 | 说明 |
|---|---|---|---|
| 0 | VER | 1 | 协议版本号(0x05) |
| 1 | CMD | 1 | 请求类型 |
| 2 | RSV | 1 | 保留字段,必须为0 |
| 3 | ATYP | 1 | 地址类型(IPv4、IPv6或域名) |
| 4 | DST.ADDR | 可变 | 目标地址,根据ATYP字段的不同而变化 |
| … | DST.PORT | 2 | 目标端口,网络字节序(大段序) |
VER:协议版本号
CMD:请求类型
- 0x01:CONNECT请求,用于TCP连接
- 0x02:BIND请求,用于监听连接(很少使用)
- 0x03:UDP ASSOCIATE请求,用于UDP连接
RSV:保留字段,固定为0x00
ATYP:目标地址类型,指定目标地址的格式:
- 0x01:IPv4地址,4字节
- 0x03:域名,后跟1字节长度字段,和实际域名字符串。
- 0x04:IPv6地址,16字节
DST.ADDR:目标地址,格式由ATYP决定
DST.PORT:目标端口,占用2字节
代理服务器的响应请求数据包
| 字节偏移量 | 字段名称 | 字节长度 | 说明 |
|---|---|---|---|
| 0 | VER | 1 | 协议版本号(0x05) |
| 1 | REP | 1 | 应答字段,表示请求结果 |
| 2 | RSV | 1 | 保留字段,必须为0 |
| 3 | ATYP | 1 | 地址类型(与请求数据包格式相同) |
| 4 | DST.ADDR | 可变 | 服务器绑定的地址 |
| … | DST.PORT | 2 | 服务器绑定的端口 |
VER:协议版本号,固定为 0x05。
REP:应答代码,表示请求的处理结果:
- 0x00:成功。
- 0x01:通用SOCKS服务器故障。
- x02:连接不允许。
- 0x03:网络不可达。
- 0x04:主机不可达。
- 0x05:连接被拒绝。
- 0x06:TTL超时。
- 0x07:不支持的命令。
- 0x08:不支持的地址类型。
- 其他:保留。
RSV:保留字段,固定为 0x00。
ATYP:地址类型,与请求数据包中的ATYP字段含义相同。
BND.ADDR:代理服务器绑定的地址,通常是代理服务器的IP地址或域名。
BND.PORT:代理服务器绑定的端口。
SOCKS5与其他代理的区别
- 与HTTP代理:HTTP代理只支持HTTP/HTTPS流量,而socks5支持多种协议(TCP/UDP),适用范围更广。
- 与SOCKS4代理:SOCKS4不支持认证和UDP流量,功能较为简单
- 与VPN:VPN对全部网络流量进行加密,socks5本身不加密数据,需要和加密协议配合使用,socks5仅代理特定应用的流量,适合轻量代理
欢迎读者批评指正!!!