把错误处理全拿掉,看的方便点 然后主要是感觉整个go中err处理太多了,而且全都是用if写的,导致看起来很繁杂 最后是感觉这些库还是得看代码引用的提示和文档, 主要课内讲的都是流程。
猜数字
很简单的随机生成和输入操作
bufio库的使用
reader := bufio.NewReader(os.stdin)
通过对底层os库的封装实现的数据流运行
input, err := reader.ReadString('\n')
调用方法, 以某种形式读取数据
基础标准库
strconv
主要是字符和其他类型的转化
strconv.Atoi(s string) int
转换 string to int
strings
字符操作
strings.TrimSuffix(s string , suffix string) string
删除末尾字符, 如果没有就正常返回
简单字典
网络库和 json 转化
go 中需要有一个结构体接受 json 的转化, 所以 struct 需要提前准备, 课中使用了两个工具
curlconverter 根据网站的原生请求生成对应的 go 语言请求
oktools 根据 json 自动生成 struct
结构体可以用 `` 标注, 表面转化为 json 以后的名字 比如
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
这样转化后就是对应的标注
网络库 net/http
go 标准库
client := &http.Client{}
初始化一个请求客户端
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
用来创建一个请求, 显而易见的结构
请求形式, url , 请求体 清晰可见
req.Header.Set("authority", "api.interpreter.caiyunai.com")
设置请求头, 这里是用网站生成的
resp, err := client.Do(req)
进行请求
bodyText, err := io.ReadAll(resp.Body)
err = json.Unmarshal(bodyText, &dictResponse)
获取 json , 反序列化
os库
被这个项目用来获取环境参数
word := os.Args[1]
第一个是执行文件或者代码
接下去的就是携带参数
socks5代理
主要是对于 net 库的接受信息的解析, 如何取出其中的值, 并判断是否符合要求, 然后是连接, 用 context 库进行进程管理
go process(client)
用 goroutine 后台开启一个服务器交互进程
- 接受请求并鉴权 —— auth 函数
- 链接需求网址并通讯 —— connect 函数
鉴权
首先要进行的就是 auth 也就是鉴权, 在这个阶段我们要做 2 件事情
- 判断协议是否被支持
- 判断能否通过认证
下面是在鉴权阶段我们拿到的东西
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
VER: 协议版本, socks5为0x05
NMETHODS: 支持认证的方法数量
METHODS: 对应NMETHODS, NMETHODS的值为多少, METHODS就有多少个字节。RFC预定义了一些值的含义, 内容如下:
X’00’ NO AUTHENTICATION REQUIRED
X’02’ USERNAME/PASSWORD
了解通信过程, 整体就是在判断err, 然后回包告诉客户端认证成功了。
取出
ver, err := reader.ReadByte()
methodSize, err := reader.ReadByte()
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
手动回包
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, err = conn.Write([]byte{socks5Ver, 0x00})
通讯
connect阶段也就两个主要模块
- 判断是否这个请求服务的能接受, 判断是什么类型的目标地址并解析
- 通信
下面是通讯阶段拿到的东西, 和鉴权中不一样的主要是多了目标地址
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
VER 版本号, socks5的值为0x05
CMD 0x01表示CONNECT请求
RSV 保留字段, 值为0x00
ATYP 目标地址类型, DST.ADDR的数据对应这个字段的类型。
0x01表示IPv4地址, DST.ADDR为4个字节
0x03表示域名, DST.ADDR是一个可变长度的域名
DST.ADDR 一个可变长度的值
DST.PORT 目标端口, 固定2个字节
--这个是课内部分, 但是这个域名形式的会先返回一个hostsize然后才是host--
鉴权
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
ver, cmd, atyp := buf[0], buf[1], buf[3]
然后做个解析
addr := ""
switch atyp {
case atypeIPV4:
_, err = io.ReadFull(reader, buf)
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize, err := reader.ReadByte() //这里是host和v4地址 结构不一样的地方
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
addr = string(host)
取端口号, 这里是用了切片和数组的对应关系
_, err = io.ReadFull(reader, buf[:2])
port := binary.BigEndian.Uint16(buf[:2])
第二步, 该给目标地址发送连接请求了
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
然后给客户端回个包,从这里也能看出来我们拿到的数据是怎么样的
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本, 这里为0x05
// REP Relay field, 内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.PORT
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
然后将客户端和目标做互相拷贝, 这里开启两个进程, 同时用context库进行进程交互
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
当有一边读取不到东西阻塞后, 用 cancel() 将两个进程都关闭
用信道保持代码不继续运行, 被关闭之后继续往下走
net库
开在指定端口开一个服务器, 并能够监听收集数据
server, err := net.Listen("tcp", "127.0.0.1:1080")
开服务器, 指定协议和端口
client, err := server.Accept()
拿到关键 client 这是个 conn 网络连接, 读数据的
reader := bufio.NewReader(conn)
数据取出, 开始操作
_, err = conn.Write([]byte{socks5Ver, 0x00})
给客户端回请求, 这里一般就是用 0x00 表示成功
context库
ctx, cancel := context.WithCancel(context.Background())
通过默认的 Background 创建一个父节点, 并衍生一个 Cancel context
在关闭父节点就会直接关闭所有子节点
通过传递和使用 ctx 和 cancel , 就可以对不同 goruntime 进行控制
ctx.Done()
表示是否 ctx 被关闭, 关闭后返回一个被关闭的 channel