Go语言基础-基础语法| 青训营笔记

171 阅读9分钟

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

课堂笔记(走进Go语言基础语言)

本节课重点内容: 介绍了go语言的基础语法与常用函数

将自己认为容易遗忘或重要的知识点记录下来:

  • go语言是静态编译的
  • go语言可以跨平台
  • go语言是强类型语言,go语言字符串可以直接通过“+”进行拼接
  • 变量声明方式有两种:使用var或:=
  • 常量声明方式:const,常量没有确定的类型,根据使用的上下文自动确定类型
  • 在for循环中,可以使用break跳出循环或使用continue继续循环
  • switch可以使用任意变量类型,可以在case中写条件分支
  • 数组长度固定,更常使用切片(slice)
  • 使用make创建slice,使用append追加元素,扩容会返回一个新的slice,可以使用copy在两个slice之间拷贝
  • slice的切片操作是左开右闭(从0开始)
  • 使用make创建map,使用delete删除键值对,map完全无序
  • 对slice和map可以使用range快速遍历,对于slice返回索引和值,对于map返回key和value
  • go的函数原生支持返回多个值
  • 指针可以对传入的参数进行修改
  • 函数需要传入指针才能修改传入的值,否则修改的是拷贝的值
  • 结构体是带类型的字段的集合 ,结构体也可以作为函数的参数
  • 结构体也可以使用指针,可以避免大结构体拷贝的开销
  • 可以为结构体定义方法,定义方法时带指针可以对结构体进行修改
  • go语言一般使用单独的返回值处理错误(error)
  • strings包包含很多处理字符串的函数(看文档)
  • fmt包中包含许多字符串格式化的方法(看文档),%v、%+v、%#v、%.2f
  • 如果想将结构体序列化为json格式,字段需要大写,序列化(json.Marshal)后为byte数组,序列化后的字符串也可以反序列化(json.Unmarshal)到一个空变量中,输出字段为大写,如需小写需使用tag
  • go语言使用time包对时间进行处理(看文档)
  • go语言使用strconv包对字符串和数字进行转换(看文档)
  • os.Args获取进程的命令行参数
  • os.Getenv获取环境变量
  • os.Setenv写入环境变量
  • exec.Command快速启动子进程并获取其输入输出

课堂笔记(Go语言实战案例)

本节课重点内容:通过三个案例更加熟悉go语言

1. 猜谜游戏

  1. 生成随机数需要设置随机数种子,可使用时间戳作为随机数种子
  2. 使用bufio.NewReader(os.Stdin)将读取用户输入并转换为只读的流,可以对流进行一些操作
  3. 使用bufio.ReadString('\n')函数从流中读取一行
  4. strings.Trim(input, "\r\n")去掉这一行首部和尾部的换行和回车(课程中使用strings.TrimSuffix(input, "\n")将这一行的末尾换行符删除,在我的windows环境下不论输入什么都显示错误值)
  5. 使用 guess, err := strconv.Atoi(input)转换为数字
  6. 加入for循环保证程序可以持续运行,否则只能输入一次程序就结束,使用continue而不是return保证出错时程序不会退出
package main

import (
   "bufio"
   "fmt"
   "math/rand"
   "os"
   "strconv"
   "strings"
   "time"
)

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. 在线词典

  1. 黑魔法!使用代码生成!链接可以通过json自动生成请求代码、链接可以通过json自动生成对应的结构体
  2. client := &http.Client{}创建http的客户端,创建时可以指定一些参数
  3. 通过bytes.NewReader()创建流,通过http.NewRequest()创建请求(使用流是因为可能文件较大,消耗大量内存)
  4. 通过cilent.Do()发起请求,resp.Body.Close()将返回的resp中的Body流关闭,并使用ioutil.ReadAll(resp.Body)将Body流读入内存并转换为byte数组(json字符串)
  5. 构造结构体,使之与请求的json格式字段一一对应,并将其序列化为json的byte数组,并使用bytes.NewReader()将此byte数组转换为流
  6. 构造结构体,使之与返回的Response的json格式字段一一对应,将json字符串反序列化到此结构体中(使用代码生成)
  7. 通过resp.StatusCode查看状态码,确认响应是否正确
package main

import (
   "bytes"
   "encoding/json"
   "fmt"
   "io/ioutil"
   "log"
   "net/http"
   "os"
)

type DictRequest struct {
   TransType string `json:"trans_type"`
   Source    string `json:"source"`
   UserID    string `json:"user_id"`
}

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

func query(word string) {
   client := &http.Client{}
   request := DictRequest{TransType: "en2zh", Source: word}
   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)
   }
   req.Header.Set("Connection", "keep-alive")
   req.Header.Set("DNT", "1")
   req.Header.Set("os-version", "")
   req.Header.Set("sec-ch-ua-mobile", "?0")
   req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
   req.Header.Set("app-name", "xy")
   req.Header.Set("Content-Type", "application/json;charset=UTF-8")
   req.Header.Set("Accept", "application/json, text/plain, */*")
   req.Header.Set("device-id", "")
   req.Header.Set("os-type", "web")
   req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
   req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
   req.Header.Set("Sec-Fetch-Site", "cross-site")
   req.Header.Set("Sec-Fetch-Mode", "cors")
   req.Header.Set("Sec-Fetch-Dest", "empty")
   req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
   req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
   req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
   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)
   }
   if resp.StatusCode != 200 {
      log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
   }
   var dictResponse DictResponse
   err = json.Unmarshal(bodyText, &dictResponse)
   if err != nil {
      log.Fatal(err)
   }
   fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
   for _, item := range dictResponse.Dictionary.Explanations {
      fmt.Println(item)
   }
}

func main() {
   if len(os.Args) != 2 {
      fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
      `)
      os.Exit(1)
   }
   word := os.Args[1]
   query(word)
}

3. SOCKS5代理

  1. SOCKS5代理原理 捕获.PNG
  2. 使用net.Listen()监听端口,使用server.Accept()接受请求,创建协程调用函数处理该请求
  3. 使用reader.ReadByte()每次读取一个字节,通过conn.Write()将字节写入
  4. 构建auth函数,协议的认证阶段,参数为*bufio.Reader、net.Conn,返回error,auth函数首先判断版本号是否正确,再判断methodSize是否正确,通过methodSize创建缓冲区,使用io.ReadFull将reader剩余字节(methods)填充进这个缓冲区,打印版本号与method信息,将协议版本号与选择的连接方式写入conn
  5. 构建connect函数,协议的请求阶段,签名与auth函数相同,创建长度为4的缓冲区,使用reader填充缓冲区,也就是读取前四个字段(前四个字段定长4字节),验证ver、cmd、atyp的合法性,判断atyp的类型,不同的类型读取不同的字节数(BND.ADDR),通过对缓冲区切片读取最后两位字节(DST.PORT),在通过binary.BigEndian.Uint16()将缓冲区的DST.PORT解析为整型数字,将一些字段信息写入conn
  6. 使用net.Dial()与对应的ip地址+端口号建立tcp连接
  7. 使用io.Copy(dst Writer, src Reader) (written int64, err error)实现数据的单向转发,将只读流(Readr)里面的数据逐步拷贝到可写流中(Writer),构建两个协程,一个协程从用户的游览器拷贝数据到底层服务器,另一个从底层服务器拷贝数据到用户的游览器,使用context机制(go语言重要机制!!!)使两个协程中某一协程出错时调用cancel()函数,也就是任意方向的copy()失败时返回concel()函数并将连接关闭(defer concel防御式编程,concel()方法的功能就是关闭channel) context机制不懂,需要课后自己学习
package main

import (
   "bufio"
   "context"
   "encoding/binary"
   "errors"
   "fmt"
   "io"
   "log"
   "net"
)

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
}