GO语言基础课程笔记| 青训营笔记

57 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

猜字游戏

步骤1:定义最大数字,使用时间戳生成随机数种子,生成随机数
步骤2:使用fmt.Scanf("%d",&guess)在控制台输入我们猜的数
步骤3:如果有错误,则输出数字有误,如果没有错误,判断guess和答案是否相等,如果相等则break,如果guess大于答案则提示guess大于答案,小于也一样。最后再增加一个循环使得玩家不断猜字,直到猜到数字退出。

func main() {
   maxNum := 100
   rand.Seed(time.Now().UnixNano())
   secretNumber := rand.Intn(maxNum)
   fmt.Println("The secret number is ", secretNumber)

   fmt.Println("Please input your guess")
   //reader := bufio.NewReader(os.Stdin)
   for {
      /*input, err := reader.ReadString('\n')
      if err != nil {
         fmt.Println("An error occured while reading input. Please try again", err)
         continue
      }
      input = strings.TrimSuffix(input, "\r\n")

      guess, err := strconv.Atoi(input)*/
      var guess int
      _, err := fmt.Scanf("%d", &guess)
      if err != nil {
         fmt.Println("Invalid input. Please enter an integer value")
         continue
      }
      fmt.Println("You guess is", guess)
      if guess > secretNumber {
         fmt.Println("Your guess is bigger than the secret number. Please try again")
      } else if guess < secretNumber {
         fmt.Println("Your guess is smaller than the secret number. Please try again")
      } else {
         fmt.Println("Correct, you Legend!")
         break
      }
   }
}

翻译

1.抓包 ai.youdao.com/product-fan…

image.png 以CURL(bash)格式复制本次请求的内容,并到curlconverter.com/#go生成go语言代码 数据的返回格式为q=word&from=Auto&to=Auto不需要生成DictRequest结构体

image.png 2.生成DictResponse 结构体 复制响应中的数据到oktools.net/json2go 进行转换

image.png

创建DictResponse 结构体 并将json数据反序列化

bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
   log.Fatal(err)
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)

输出

fmt.Println(word + "翻译:")
for _, item := range dictResponse.Translation {
   fmt.Println(item)
}

实现socks5代理服务器

SOCKS5 是一个代理协议,它在使用[TCP/IP协议]通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网)中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。

// +----+----------+----------+
// |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

image.png 1.生成一个TCP服务器



func main() {
   server, err := net.Listen("tcp", "127.0.0.1:1080")
   if err != nil {
      panic(err)
   }
   for {
      client, err := server.Accept()
      if err != nil {
         log.Printf("Accept failed %v", err)
         continue
      }
      go process(client)
   }
}

func process(conn net.Conn) {
   defer conn.Close()
   reader := bufio.NewReader(conn)
   for {
      b, err := reader.ReadByte()
      if err != nil {
         break
      }
      _, err = conn.Write([]byte{b})
      if err != nil {
         break
      }
   }
}

2.认证阶段 接收version和methodSize

ver, err := reader.ReadByte()
if err != nil {
   return fmt.Errorf("read ver failed:%w", err)
}
if ver != socks5Ver {
   return fmt.Errorf("not supported ver:%v", ver)
}
methodSize, err := reader.ReadByte()
if err != nil {
   return fmt.Errorf("read methodSize failed:%w", err)
}

创建一个切片读取mothod

method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)

返回一个00 response

_, err = conn.Write([]byte{socks5Ver, 0x00})

第三步 请求阶段 (1)获取rearder的内容并存到buf中

buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
// +----+-----+-------+------+----------+----------+
// |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1  |  1  | X'00' |  1   | Variable |    2     |
// +----+-----+-------+------+----------+----------+
ver, cmd, atyp := buf[0], buf[1], buf[3]

(2)如果ver不是socks5以及cmd不是0x01则返回不支持的错误
(3)根据atyp选择不同方法获取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 = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})

第一个是版本号,第二个是返回的类型,第三个是保留字段,第四个是atyp

第四步 真正与端口建立连接,双向转发数据 标准库中的io.copy只能实现单向传输,所以我们需要使用两个io.copy.由于connect函数会立即返回,但我们需要等待某一个copy没有数据传输后关闭这时可以使用context.WithCancel实现<-ctx.Done()需要等待cancel的执行才会返回

if err != nil {
   return fmt.Errorf("write failed: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
   _, _ = io.Copy(dest, reader)
   cancel()
}()
go func() {
   _, _ = io.Copy(conn, dest)
   cancel()
}()

<-ctx.Done()
return nil