Go语言基础 | 青训营笔记

98 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
主题:Go语言基础实战

1.猜谜游戏

介绍

在这个游戏中,程序首先生成一个介于1到100之间的随机整数,然后提示玩家进行猜测,玩家每输入一个数字,程序会告诉玩家猜测的数值是高于还是低于神秘随机数,并且让玩家再次猜测,如果猜对了,就告诉玩家胜利并且退出程序。

功能实现

a.生成随机数

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

b.用时间戳初始化随机数种子生成随机数

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

c.读取用户输入

fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')  //第一行输入
if err != nil {
   fmt.Println("An error occured while reading input. Please try again", err)
   return
}
input = strings.Trim(input, "\r\n")   //去掉换行符

guess, err := strconv.Atoi(input)   //转换成数字
if err != nil {
   fmt.Println("Invalid input. Please enter an integer value")
   return
}
fmt.Println("You guess is", guess)

d.判断逻辑

if err != nil {
   fmt.Println("Invalid input. Please enter an integer value")
   return
}
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!")
}

e.实现游戏循环

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.Trim(input, "\r\n")

      guess, err := strconv.Atoi(input)
      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
      }
   }
}

2.命令行词典

介绍

用户可以在命令行里查询一个单词,程序通过第三方api查询到单词的翻译并打印处理。重点学习发送http请求,解析json文件等功能实现。

功能实现

a.抓包

翻译api:fanyi.caiyunapp.com/
过程:f12查找Post请求,查看http请求的格式和返回的json文件。

b.代码生成

生成http请求。
代码生成网址: curlconverter.com/#go
找到post请求,复制为curl(bush),复制到以上网址中,生成代码。

package main

import (
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
  "strings"
)

func main() {
  client := &http.Client{}
  var data = strings.NewReader(`{"trans_type":"en2zh","source":"goo"}`)
  req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)  //创建请求
  if err != nil {
     log.Fatal(err)
  }  
  req.Header.Set("authority", "api.interpreter.caiyunai.com")  //开始设置请求头
  req.Header.Set("accept", "application/json, text/plain, */*")
  req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
  req.Header.Set("app-name", "xy")
  req.Header.Set("content-type", "application/json;charset=UTF-8")
  req.Header.Set("device-id", "")
  req.Header.Set("origin", "https://fanyi.caiyunapp.com")
  req.Header.Set("os-type", "web")
  req.Header.Set("os-version", "")
  req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
  req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"`)
  req.Header.Set("sec-ch-ua-mobile", "?0")
  req.Header.Set("sec-ch-ua-platform", `"Windows"`)
  req.Header.Set("sec-fetch-dest", "empty")
  req.Header.Set("sec-fetch-mode", "cors")
  req.Header.Set("sec-fetch-site", "cross-site")
  req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76")
  req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
      resp, err := client.Do(req)   //发起请求
  if err != nil {
     log.Fatal(err)
  }
  defer resp.Body.Close()
  bodyText, err := ioutil.ReadAll(resp.Body)  //读取相应
  if err != nil {
     log.Fatal(err)
  }
  fmt.Printf("%s\n", bodyText)
}

c.生成request body

type DictRequest struct {
  TransType string `json:"trans_type"`
  Source    string `json:"source"`
  UserID    string `json:"user_id"`
}
func main() {
  client := &http.Client{}
  request := DictRequest{TransType: "en2zh", Source: "good"}
  buf, err := json.Marshal(request)
  if err != nil {
     log.Fatal(err)
  }
  var data = bytes.NewReader(buf)
  req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
  if err != nil {
     log.Fatal(err)
  }
  //请求头
  //...
}

d. 解析response body

type DictResponse struct {
   Rc   int `json:"rc"`
   Wiki struct {
      KnownInLaguages int `json:"known_in_laguages"`
      Description     struct {
         Source string      `json:"source"`
         Target interface{} `json:"target"`
      } `json:"description"`
      ID   string `json:"id"`
      Item struct {
         Source string `json:"source"`
         Target string `json:"target"`
      } `json:"item"`
      ImageURL  string `json:"image_url"`
      IsSubject string `json:"is_subject"`
      Sitelink  string `json:"sitelink"`
   } `json:"wiki"`
   Dictionary struct {
      Prons struct {
         EnUs string `json:"en-us"`
         En   string `json:"en"`
      } `json:"prons"`
      Explanations []string      `json:"explanations"`
      Synonym      []string      `json:"synonym"`
      Antonym      []string      `json:"antonym"`
      WqxExample   [][]string    `json:"wqx_example"`
      Entry        string        `json:"entry"`
      Type         string        `json:"type"`
      Related      []interface{} `json:"related"`
      Source       string        `json:"source"`
   } `json:"dictionary"`
}

json结构解析: oktools.net/json2go

e.打印结果

image.png

3.SOCKS5代理

介绍

一个SOCKS5代理服务器,socks5协议是明文传输,让授权的用户可以通过单个端口去访问内部所有资源。

SOCKS5协议原理

握手阶段

浏览器向SOCKS5代理发送请求,内容包括协议的版本号、支持的认证种类等,socks5服务器选中一个认证方式返回给浏览器。返回00不需要认证,返回其他类型会进行认证。

认证阶段

不具体描述。

请求阶段

认证通过后,浏览器会对socks5服务器发起请求,主要发送版本号、请求类型(一般是connection),代表代理服务器要和某个域名或者某个IP地址某个端口进行TCP链接,代理服务器收到响应之后,会真正和后端服务器建立链接,然后返回一个响应。

relay阶段

浏览器正常发送请求,代理服务器收到请求后把请求转换到真正的服务器上。真正的服务器进行响应,会把请求转发到浏览器。代理服务器并不在意流量细节,可以是HTTP流量,也可以是其他TCP流量。

功能实现

a.TCP echo server

发送什么回什么。 image.png

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
      }
   }
}

b.auth 认证

image.png image.png


const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
   err := auth(reader, conn)
   if err != nil {
      log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
      return
   }
   log.Println("auth success")
}

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
   // +----+----------+----------+
   // |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

   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)
   }
   method := make([]byte, methodSize)
   _, err = io.ReadFull(reader, method)
   if err != nil {
      return fmt.Errorf("read method failed:%w", err)
   }
   log.Println("ver", ver, "method", method)
   // +----+--------+
   // |VER | METHOD |
   // +----+--------+
   // | 1  |   1    |
   // +----+--------+
   _, err = conn.Write([]byte{socks5Ver, 0x00})
   if err != nil {
      return fmt.Errorf("write failed:%w", err)
   }
   return nil
}

c. 请求阶段

image.png image.png

const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
  err := auth(reader, conn)
  if err != nil {
     log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
     return
  }
  err = connect(reader, conn)
  if err != nil {
     log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
     return
  }
}

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
  // +----+----------+----------+
  // |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

  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)
  }
  method := make([]byte, methodSize)
  _, err = io.ReadFull(reader, method)
  if err != nil {
     return fmt.Errorf("read method failed:%w", err)
  }

  // +----+--------+
  // |VER | METHOD |
  // +----+--------+
  // | 1  |   1    |
  // +----+--------+
  _, err = conn.Write([]byte{socks5Ver, 0x00})
  if err != nil {
     return fmt.Errorf("write failed:%w", err)
  }
  return nil
}

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
}

d.relay阶段

image.png image.png

const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
   err := auth(reader, conn)
   if err != nil {
      log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
      return
   }
   err = connect(reader, conn)
   if err != nil {
      log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
      return
   }
}

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
   // +----+----------+----------+
   // |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

   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)
   }
   method := make([]byte, methodSize)
   _, err = io.ReadFull(reader, method)
   if err != nil {
      return fmt.Errorf("read method failed:%w", err)
   }

   // +----+--------+
   // |VER | METHOD |
   // +----+--------+
   // | 1  |   1    |
   // +----+--------+
   _, err = conn.Write([]byte{socks5Ver, 0x00})
   if err != nil {
      return fmt.Errorf("write failed:%w", err)
   }
   return nil
}

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])

   dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
   if err != nil {
      return fmt.Errorf("dial dst failed:%w", err)
   }
   defer dest.Close()
   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)
   }
   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
}

用switchyOmega设置代理 image.png image.png