Go实战案例| 青训营笔记

93 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天

1.猜谜游戏

1.1生成随机数

image.png

  • 生成随机数需要使用math/rand包
  • 使用如上图的代码我们会发现每次都会生成相同的随机数(这并不是我们想要的结果)。这是为什么呢?我们接着往下看

image.png 官方文档上说明使用rand需要设置随机数种子,否则每次都会生成相同的数,我们可以采用用程序启动的时间戳来初始化随机数种子

1.2读取用户输入

获取完随机数后,我们需要读取用户的输入并转换为数字 image.png 如上图: 12行代码为读取一行输入 代码读取后会多出一个换行符,需要去掉换行符(17行代码) 使用strconv.Atoi转换成数字

1.3实现判断逻辑

读取完用户的输入之后,我们需要把读取到的数字与生成的随机数进行比较,是大是小还是相等,这里使用简单的if-else循环就可以实现,如下图。到现在,我们的程序可以正常运行,但我们只能玩一次(即只能猜测一次,无论正确与否程序都会退出),为了能一直玩下去(即猜测多次,直到猜对为止),我们需要使用循环。

image.png

1.4实现循环

image.png 把return的地方改成continue,并最后再break,原因是出错之后会继续执行而不会直接返回,直到最后把结果猜对才结束循环(break)

1.5运行结果

image.png

2.在线词典

2.1生成代码解读

image.png

  • 14行代码:创建一个post类型的http请求
  • 18-35行代码:设置请求头
  • 36行代码:发起请求
  • 40行代码:避免资源泄露手动关闭流
  • 41行代码:流读入内存中转换为byte数组
  • 问题:13行代码的输入是固定输入,这不是我们想要的结果,我们要使用变量来进行输入,需要使用json序列化,接着往下看

2.2生成request body

image.png 先声明一个结构体(如12-16行代码) 初始化结构体(20行代码) 序列化(21行代码)

2.3解析request body

自动生成结构体 image.png

2.4打印结果

image.png

  • 打印结果(99-102行代码)
  • 增加91-93行代码目的是当状态码不是200的时候打印状态码和报文,方便我们排查问题

2.5继续完善

image.png 现在的编码依旧是硬编码,我们需要改成变量,54行代码的word有query函数中word传进来

3.socks5代理

3.1socks5代理介绍

Socks5协议是一款广泛使用的代理协议,它在使用TCP/IP协议通讯的客户端和服务器之间扮演一个中介角色,使得内部网中的客户端变得能够访问Internet网中的服务器,或者使C/S(Client和Server)之间的通讯更加安全。SOCKS5 代理服务器通过将客户端发来的请求转发给真正的目标服务器, 模拟了一个客户端请求操作。在这里,客户端和SOCKS5代理服务器之间也是通过TCP/IP协议进行通讯,客户端将原本要发送给真正服务器的请求先发送给SOCKS5服务器,然后SOCKS5服务器再将请求转发给真正的服务器。

image.png

3.2socks5原理

image.png

  1. 首先客户端向代理服务器发出请求信息,用以协商版本和认证方法。随后代理服务器应答,将选择的方法发送给客户端。
  2. 客户端和代理服务器进入由选定认证方法所决定的子协商过程,子协商过程结束后,客户端发送请求信息,其中包含目标服务器的IP地址和端口。代理服务器验证客户端身份,通过后会与目标服务器连接,目标服务器经过代理服务器向客户端返回状态响应。
  3. 连接完成后,代理服务器开始作为中转站中转数据。

3.3socks5代理-TCP echo server

image.png

  • net.Listen监听端口
  • 在死循环中接收一个请求
  • process函数实现:
  1. 函数退出连接关闭(24行代码)
  2. 基于conn连接创建一个缓冲流(25行代码)
  3. 在for死循环中使用reader.ReaderByte每次读取一个字节(26-30行代码)
  4. 写入(31行代码)

3.4socks5代理-auth

3.4.1代码实现

image.png

3.4.2运行

image.png

image.png

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运行

image.png

image.png

3.6socks5代理-relay阶段

3.6.1代码实现

image.png image.png

3.6.2运行

image.png image.png

4.参考资料

字节内部课:后端入门-Go语言原理与实践

5.总结

通过猜谜游戏、词典、socks5代理这三个Go实战案例,对之前学习的Go的基础语法也有了更深的了解和认识。

看到这篇文章的掘友们,如文章有问题欢迎指正并在评论区进行讨论,望共同努力进步,感谢你们的支持点赞!