这是我参与「第五届青训营 」伴学笔记创作活动的第3天
1.猜谜游戏
1.1生成随机数
- 生成随机数需要使用math/rand包
- 使用如上图的代码我们会发现每次都会生成相同的随机数(这并不是我们想要的结果)。这是为什么呢?我们接着往下看
官方文档上说明使用rand需要设置随机数种子,否则每次都会生成相同的数,我们可以采用用程序启动的时间戳来初始化随机数种子
1.2读取用户输入
获取完随机数后,我们需要读取用户的输入并转换为数字
如上图:
12行代码为读取一行输入
代码读取后会多出一个换行符,需要去掉换行符(17行代码)
使用strconv.Atoi转换成数字
1.3实现判断逻辑
读取完用户的输入之后,我们需要把读取到的数字与生成的随机数进行比较,是大是小还是相等,这里使用简单的if-else循环就可以实现,如下图。到现在,我们的程序可以正常运行,但我们只能玩一次(即只能猜测一次,无论正确与否程序都会退出),为了能一直玩下去(即猜测多次,直到猜对为止),我们需要使用循环。
1.4实现循环
把return的地方改成continue,并最后再break,原因是出错之后会继续执行而不会直接返回,直到最后把结果猜对才结束循环(break)
1.5运行结果
2.在线词典
2.1生成代码解读
- 14行代码:创建一个post类型的http请求
- 18-35行代码:设置请求头
- 36行代码:发起请求
- 40行代码:避免资源泄露手动关闭流
- 41行代码:流读入内存中转换为byte数组
- 问题:13行代码的输入是固定输入,这不是我们想要的结果,我们要使用变量来进行输入,需要使用json序列化,接着往下看
2.2生成request body
先声明一个结构体(如12-16行代码)
初始化结构体(20行代码)
序列化(21行代码)
2.3解析request body
自动生成结构体
2.4打印结果
- 打印结果(99-102行代码)
- 增加91-93行代码目的是当状态码不是200的时候打印状态码和报文,方便我们排查问题
2.5继续完善
现在的编码依旧是硬编码,我们需要改成变量,54行代码的word有query函数中word传进来
3.socks5代理
3.1socks5代理介绍
Socks5协议是一款广泛使用的代理协议,它在使用TCP/IP协议通讯的客户端和服务器之间扮演一个中介角色,使得内部网中的客户端变得能够访问Internet网中的服务器,或者使C/S(Client和Server)之间的通讯更加安全。SOCKS5 代理服务器通过将客户端发来的请求转发给真正的目标服务器, 模拟了一个客户端请求操作。在这里,客户端和SOCKS5代理服务器之间也是通过TCP/IP协议进行通讯,客户端将原本要发送给真正服务器的请求先发送给SOCKS5服务器,然后SOCKS5服务器再将请求转发给真正的服务器。
3.2socks5原理
- 首先客户端向代理服务器发出请求信息,用以协商版本和认证方法。随后代理服务器应答,将选择的方法发送给客户端。
- 客户端和代理服务器进入由选定认证方法所决定的子协商过程,子协商过程结束后,客户端发送请求信息,其中包含目标服务器的IP地址和端口。代理服务器验证客户端身份,通过后会与目标服务器连接,目标服务器经过代理服务器向客户端返回状态响应。
- 连接完成后,代理服务器开始作为中转站中转数据。
3.3socks5代理-TCP echo server
- net.Listen监听端口
- 在死循环中接收一个请求
- process函数实现:
- 函数退出连接关闭(24行代码)
- 基于conn连接创建一个缓冲流(25行代码)
- 在for死循环中使用reader.ReaderByte每次读取一个字节(26-30行代码)
- 写入(31行代码)
3.4socks5代理-auth
3.4.1代码实现
3.4.2运行
3.5socks5代理-请求阶段
3.5.1代码实现
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+-----+-------+------+----------+----------+
// |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个字节
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
ver, cmd, atyp := buf[0], buf[1], buf[3]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
addr := ""
switch atyp {
case atypIPV4:
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read hostSize failed:%w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host failed:%w", err)
}
addr = string(host)
case atypeIPV6:
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])
log.Println("dial", 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})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
return nil
}
3.5.2运行
3.6socks5代理-relay阶段
3.6.1代码实现
3.6.2运行
4.参考资料
字节内部课:后端入门-Go语言原理与实践
5.总结
通过猜谜游戏、词典、socks5代理这三个Go实战案例,对之前学习的Go的基础语法也有了更深的了解和认识。
看到这篇文章的掘友们,如文章有问题欢迎指正并在评论区进行讨论,望共同努力进步,感谢你们的支持点赞!