day1_Go 语言上手-基础语言|青训营笔记

125 阅读17分钟

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

课前准备:

  • 1.安装 Go 语言
  • 2.配置 Go 语言开发环境
  • 3.下载课程示例代码

学习路线

代码跟着敲一遍基本就会啦

1.Hello
package main

import (
   "fmt"
)

func main() {
   fmt.Println("Hello World")
}

  1. 必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过
  2. main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main 函数 也很特殊,它是整个程序执行时的入口
  3. 一个函数的声明由func关键字、函数名、参数列表、返回值列表(这个例子里的main函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成
  4. Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析
2.变量声明
package main

import (
   "fmt"
   "math"
)

func main() {
   var a = "initial"
   g := a + "foo"
   var b, c int = 1, 2
   var d = true
   var e float64
   f := float32(e)

   fmt.Println(a, b, c, d, e, f)
   fmt.Println(g)

   const s string = "constant"
   const h = 50000
   const i = 3e20 / h
   fmt.Println(s, h, i, math.Sin(h), math.Sin(i))

}

//initial 1 2 true 0 0
//initialfoo
//constant 50000 6e+15 -0.9998401890897897 0.09168912942995323
3.for循环
package main

import "fmt"

func main() {
   i := 1
   //死循环
   for {
      fmt.Println("loop")
      break
   }
   for j := 7; j < 9; j++ {
      fmt.Println(j)
   }
   for n := 0; n < 5; n++ {
      if n%2 == 0 {
         continue
      }
      fmt.Println(n)
   }
   for i <= 3 {
      i++
      fmt.Println(i)
   }

}
//loop
//7
//8
//1
//3
//2
//3
//4
4.if
package main

import "fmt"

func main() {

   if 7%2 == 0 {
      fmt.Println("7 is even")
   } else {
      fmt.Println("7 is odd")
   }

   if 8%4 == 0 {
      fmt.Println("8 is divisible by 4")
   }

   if num := 9; num < 0 {
      fmt.Println(num, "is negative")
   } else if num < 10 {
      fmt.Println(num, "has 1 digit")
   } else {
      fmt.Println(num, "has multiple digits")
   }
}
5.switch
package main

import (
   "fmt"
   "time"
)

func main() {
   a := 2
   switch a {
   case 1:
      fmt.Println("a=one")
   case 2:
      fmt.Println("a=two")
   case 4, 5:
      fmt.Println("a=four or five")
   default:
      fmt.Println("other")

   }
   t := time.Now()
   switch {
   case t.Hour() < 12:
      fmt.Println("It's before noon")
   default:
      fmt.Println("It's after noon")
   }

}
//a=two
//It's after noon   
6.数组array
package main

import "fmt"

func main() {
   var a [5]int
   a[4] = 100
   fmt.Println("get:", a[2])
   fmt.Println("len:", len(a))

   b := [5]int{1, 2, 3, 4, 5}
   fmt.Println(b)

   var twoD [3][2]int
   for i := 0; i < 3; i++ {
      for j := 0; j < 2; j++ {
         twoD[i][j] = i + j
      }
   }
   fmt.Println("2d:", twoD)
}
//get: 0
//len: 5
//[1 2 3 4 5]
//2d: [[0 1] [1 2] [2 3]]
7.slice切片(变长序列)

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

package main

import "fmt"

func main() {

   s := make([]string, 3)
   s[0] = "a"
   s[1] = "b"
   s[2] = "c"
   fmt.Println("get:", s[2])   // c
   fmt.Println("len:", len(s)) // 3

   s = append(s, "d")
   s = append(s, "e", "f")
   fmt.Println(s) // [a b c d e f]

   c := make([]string, len(s))
   copy(c, s)
   fmt.Println(c) // [a b c d e f]

   fmt.Println(s[2:5]) // [c d e]
   fmt.Println(s[:5])  // [a b c d e]
   fmt.Println(s[2:])  // [c d e f]

   good := []string{"g", "o", "o", "d"}
   fmt.Println(good) // [g o o d]
}
8.map
package main

import "fmt"

func main() {
   m := make(map[string]int)
   m["one"] = 1
   m["two"] = 2
   fmt.Println(m)
   fmt.Println(len(m))
   fmt.Println(m["one"])
   fmt.Println(m["unknow"])
   //在这种场景下,map的下标语法将产生两个值;
   //第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok,特别适合马上用于if条件判断部分。
   r, ok := m["unknow"]
   fmt.Println(r, ok)

   delete(m, "one")

   m2 := map[string]int{"one": 1, "two": 2}
   var m3 = map[string]int{"one": 1, "two": 2}
   fmt.Println(m2, m3)

}
//map[one:1 two:2]
//2
//1
//0  
9.range
package main

import "fmt"

func main() {
   nums := []int{2, 3, 4}
   sum := 0
   for i, num := range nums {
      sum += num
      if num == 2 {
         fmt.Println("index:", i, "num:", num)
      }
   }
   fmt.Println(sum)

   m := map[string]string{"a": "A", "b": "B"}
   for k, v := range m {
      fmt.Println(k, v)
   }
   for k := range m {
      fmt.Println("key", k)
   }
}

//index: 0 num: 2
//9
//a A
//b B
//key a
//key b
10.func
package main

import "fmt"

func add(a int, b int) int {
   return a + b
}
func add2(a, b int) int {
   return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
   v, ok = m[k]
   return v, ok
}
func main() {
   res := add(1, 2)
   fmt.Println(res)

   v, ok := exists(map[string]string{"a": "A", "b": "B"}, "a")
   fmt.Println(v, ok)
}
//3
//A true
11.ptr
package main

import "fmt"

func add(n int) {
   n += 2
}
func add1(n *int) {
   *n += 2
}
func main() {
   n := 5
   add(n)
   fmt.Println(n)

   add1(&n)
   fmt.Println(n)

}
//5
//7
12.struct
package main

import "fmt"

type user struct {
   name     string
   password string
}

func main() {
   a := user{name: "wang", password: "1024"}
   b := user{"wang", "1024"}
   c := user{name: "wang"}
   c.password = "1024"
   var d user
   d.name = "wang"
   d.password = "1024"

   fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
   fmt.Println(checkPassword(a, "haha"))   // false
   fmt.Println(checkPassword2(&a, "haha")) // false
}

func checkPassword(u user, password string) bool {
   return u.password == password
}

func checkPassword2(u *user, password string) bool {
   return u.password == password
}
13.struct-method
// 作用于特定类型的函数,特定类型变量称为接收者(receiver).类比js中的this
/*
   func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {}
   建议:
   1. 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母
   2. 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型
   3. 方法名、参数列表、返回参数:具体格式与函数定义相同
   区别函数:
   函数不属于任何类型,方法属于特定类型
*/
// 结构体函数
// 对象方法
// 1. 指针类型的接收者: 由一个结构体的指针组成
// 由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的
// 2. 值类型的接收者: 当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。
// 在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身
/*
   什么时候使用指针接受者
   1.需要修改接收者中的值
  2.接收者是拷贝代价比较大的大对象
  3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者
*/

package main

import "fmt"

type user struct {
   name     string
   password string
}

func (u user) checkPassword(password string) bool {
   return u.password == password
}
func (u *user) resetPassword(password string) {
   u.password = password
}
func main() {
   a := user{name: "wang", password: "1024"}
   a.resetPassword("1234")
   fmt.Println(a.checkPassword("1234"))

}
//true
14.error
package main

import (
   "errors"
   "fmt"
)

type user struct {
   name     string
   password string
}

func findUser(users []user, name string) (v *user, err error) {
   for _, u := range users {
      if u.name == name {
         return &u, nil
      }
   }
   return nil, errors.New("not found")
}
func main() {
   users := []user{{"tom", "1234"}}
   u, err := findUser(users, "tom")
   if err != nil {
      fmt.Println(err)
      return
   }
   fmt.Println(u.name)
   if u, err := findUser(users, "wang"); err != nil {
      fmt.Println(err)
      return
   } else {
      fmt.Println(u.name)
   }

}

//tom
//not found
15.string
package main

import (
   "fmt"
   "strings"
)

func main() {
   a := "hello"
   fmt.Println(strings.Contains(a, "ll"))                // true
   fmt.Println(strings.Count(a, "l"))                    // 2
   fmt.Println(strings.HasPrefix(a, "he"))               // true
   fmt.Println(strings.HasSuffix(a, "llo"))              // true
   fmt.Println(strings.Index(a, "ll"))                   // 2
   fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
   fmt.Println(strings.Repeat(a, 2))                     // hellohello
   fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
   fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
   fmt.Println(strings.ToLower(a))                       // hello
   fmt.Println(strings.ToUpper(a))                       // HELLO
   fmt.Println(len(a))                                   // 5
   b := "你好"
   fmt.Println(len(b)) // 6
}
16.fmt
package main

import "fmt"

type point struct {
   x, y int
}

func main() {
   s := "hello"
   n := 123
   p := point{1, 2}
   fmt.Println(s, n) // hello 123
   fmt.Println(p)    // {1 2}

   fmt.Printf("s=%v\n", s)  // s=hello
   fmt.Printf("n=%v\n", n)  // n=123
   fmt.Printf("p=%v\n", p)  // p={1 2}
   fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
   fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

   f := 3.141592653
   fmt.Println(f)          // 3.141592653
   fmt.Printf("%.2f\n", f) // 3.14
}
17.json

JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议

package main

import (
   "encoding/json"
   "fmt"
)

type userInfo struct {
   Name  string
   Age   int `json:"age"`
   Hobby []string
}

func main() {
   a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
   //将一个Go语言中结构体转为JSON的过程叫编组(marshaling)
   buf, err := json.Marshal(a)
   if err != nil {
      panic(err)
   }
   fmt.Println(buf)
   fmt.Println(string(buf))
   //json.MarshalIndent函数将产生整齐缩进的输出。
   //该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进
   buf, err = json.MarshalIndent(a, "", "\t")
   if err != nil {
      panic(err)
   }
   fmt.Println(buf)
   fmt.Println(string(buf))
   //将JSON数据解码为Go语言的数据结构,Go语言中一般叫unmarshaling
   var b userInfo
   err = json.Unmarshal(buf, &b)
   if err != nil {
      panic(err)
   }
   fmt.Println(b)

}
//[123 34 78 97 109 101 34 58 34 119 97 110 103 34 44 34 97 103 101 34 58 49 56 44 34 72 111 98 98 121 34 58 91 34 71 111 108 97 110 103 34 44 34 84 121 112 101 83 99 114 105 112 116 34 93 125]
//{"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

//[123 10 9 34 78 97 109 101 34 58 32 34 119 97 110 103 34 44 10 9 34 97 103 101 34 58 32 49 56 44 10 9 34 72 111 98 98 121 34 58 32 91 10 9 9 34 71 111 108 97 110 103 34 44 10 9 9 34 84 121 112 101 83 99 114 105 112 116 34 10 9 93 10 125]
//{
//"Name": "wang",
//"age": 18,
//"Hobby": [
//"Golang",
//"TypeScript"
//]
//}

//{wang 18 [Golang TypeScript]}
18.time
package main

import (
   "fmt"
   "time"
)

func main() {
   now := time.Now()
   fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
   t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
   t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
   fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
   fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
   fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
   diff := t2.Sub(t)
   fmt.Println(diff)                           // 1h5m0s
   fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
   t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
   if err != nil {
      panic(err)
   }
   fmt.Println(t3 == t)    // true
   fmt.Println(now.Unix()) // 1648738080
}
19.strconv
package main

import (
   "fmt"
   "strconv"
)

func main() {
   f, _ := strconv.ParseFloat("1.234", 64)
   fmt.Println(f) // 1.234

   n, _ := strconv.ParseInt("111", 10, 64)
   fmt.Println(n) // 111

   n, _ = strconv.ParseInt("0x1000", 0, 64)
   fmt.Println(n) // 4096

   n2, _ := strconv.Atoi("123")
   fmt.Println(n2) // 123

   n2, err := strconv.Atoi("AAA")
   fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
20.env
package main

import (
   "fmt"
   "os"
   "os/exec"
)

func main() {
   // go run example/20-env/main.go a b c d
   fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
   fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
   fmt.Println(os.Setenv("AA", "BB"))

   buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf)) // 127.0.0.1       localhost
}

1.猜数字游戏

2.在线词典

发送HTTP请求,解析json,使用代码生成来提高开发效率 找到网络->全部->dict->post请求 打开彩云小译,输入单词,点击翻译

image.png

image.png

image.png

通过 curl-to-go - 编程乐园 (curlconverter.com)简化生成请求过程

Window下复制为cURL(cmd)

image.png

得到一大段json

curl "https://api.interpreter.caiyunai.com/v1/dict" ^
  -H "Accept: application/json, text/plain, */*" ^
  -H "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" ^
  -H "Connection: keep-alive" ^
  -H "Content-Type: application/json;charset=UTF-8" ^
  -H "Origin: https://fanyi.caiyunapp.com" ^
  -H "Referer: https://fanyi.caiyunapp.com/" ^
  -H "Sec-Fetch-Dest: empty" ^
  -H "Sec-Fetch-Mode: cors" ^
  -H "Sec-Fetch-Site: cross-site" ^
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50" ^
  -H "X-Authorization: token:qgemv4jr1y38jyq6vhvi" ^
  -H "app-name: xy" ^
  -H "device-id: " ^
  -H "os-type: web" ^
  -H "os-version: " ^
  -H "sec-ch-ua: ^\^" Not A;Brand^\^";v=^\^"99^\^", ^\^"Chromium^\^";v=^\^"100^\^", ^\^"Microsoft Edge^\^";v=^\^"100^\^"" ^
  -H "sec-ch-ua-mobile: ?0" ^
  -H "sec-ch-ua-platform: ^\^"Windows^\^"" ^
  --data-raw "^{^\^"trans_type^\^":^\^"en2zh^\^",^\^"source^\^":^\^"hello^\^"^}" ^
  --compressed

curl转为go代码

v1

package main

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

func main() {
 client := &http.Client{}
 //重要参数Timeout,这里没有指定
 //strings.NewReader将字符串转成流
 var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
 //创建请求
 //参数1.请求方式
 //2.URL
 //3.流data  post的body可能很大,所以通过流进行传输
 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在函数结束之后,从上往下触发
 defer resp.Body.Close()
 //把返回的流整个读到内存中,变成byte数组
 bodyText, err := ioutil.ReadAll(resp.Body)
 if err != nil {
    log.Fatal(err)
 }
 fmt.Printf("%s\n", bodyText)
}

输出json

{"rc":0,"wiki":{"known_in_laguages":63,"description":{"source":"tangible and intangible thing, except labor tied services, that satisfies human wants
 and provides utility","target":null},"id":"Q28877","item":{"source":"good","target":"\u5546\u54c1"},"image_url":"http:\/\/www.caiyunapp.com\/imgs\/l
ink_default_img.png","is_subject":"true","sitelink":"https:\/\/www.caiyunapp.com\/read_mode\/?id=625b4b949c0120504d1e7b69"},"dictionary":{"prons":{"e
n-us":"[g\u028ad]","en":"[gud]"},"explanations":["a.\u597d\u7684;\u5584\u826f\u7684;\u5feb\u4e50\u7684;\u771f\u6b63\u7684;\u5bbd\u5927\u7684;\u6709\u
76ca\u7684;\u8001\u7ec3\u7684;\u5e78\u798f\u7684;\u5fe0\u5b9e\u7684;\u4f18\u79c0\u7684;\u5b8c\u6574\u7684;\u5f7b\u5e95\u7684;\u4e30\u5bcc\u7684","n.\
u5229\u76ca;\u597d\u5904;\u5584\u826f;\u597d\u4eba","ad.=well"],"synonym":["excellent","fine","nice","splendid","proper"],"antonym":["bad","wrong","e
vil","harmful","poor"],"wqx_example":[["to the good","\u6709\u5229,\u6709\u597d\u5904"],["good, bad and indifferent","\u597d\u7684,\u574f\u7684\u548c
\u4e00\u822c\u7684"],["good innings","\u957f\u5bff"],["good and ...","\u5f88,\u9887;\u5b8c\u5168,\u5f7b\u5e95"],["do somebody's heart good","\u5bf9\u
67d0\u4eba\u7684\u5fc3\u810f\u6709\u76ca,\u4f7f\u67d0\u4eba\u611f\u5230\u6109\u5feb"],["do somebody good","\u5bf9\u67d0\u4eba\u6709\u76ca"],["be good
 for","\u5bf9\u2026\u6709\u6548,\u9002\u5408,\u80dc\u4efb"],["be good at","\u5728\u2026\u65b9\u9762(\u5b66\u5f97,\u505a\u5f97)\u597d;\u5584\u4e8e"],[
"as good as one's word","\u4fe1\u5b88\u8bfa\u8a00,\u503c\u5f97\u4fe1\u8d56"],["as good as","\u5b9e\u9645\u4e0a,\u51e0\u4e4e\u7b49\u4e8e"],["all well 
and good","\u4e5f\u597d,\u8fd8\u597d,\u5f88\u4e0d\u9519"],["a good","\u76f8\u5f53,\u8db3\u8db3"],["He is good at figures . ","\u4ed6\u5584\u4e8e\u8ba
1\u7b97\u3002"]],"entry":"good","type":"word","related":[],"source":"wenquxing"}}

v1问题:输入是固定的,这里需要输入变量 这里需要json序列化输入而不是字符串输入

v2 序列化json,只需要构造一个结构体,让结构体字段和json字段一一对应 json.Marshal返回的是byte数组

package main

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

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)
   }
   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)
   }
   fmt.Printf("%s\n", bodyText)
}

输出结果和v1一样

v3 实现对responce的反序列化

image.png

在js/py脚本里返回的是字典或map dolang是强类型语言,这样获取也可以,但是最佳方式是通过和json字段一一对应的结构体,将返回的json反序列化到结构体里

解析responce body,通过JSON转Golang Struct - 在线工具 - OKTools

image.png 转换-展开:生成独立的多个结构体 转换-嵌套:生成单个结构体

复制响应

image.png

{"rc":0,"wiki":{"known_in_laguages":19,"description":{"source":"salutation or greeting","target":null},"id":"Q12068060","item":{"source":"hello","target":"Hello"},"image_url":"http:\/\/www.caiyunapp.com\/imgs\/link_default_img.png","is_subject":"true","sitelink":"https:\/\/www.caiyunapp.com\/read_mode\/?id=5c1654ca4faac90001a6f17a"},"dictionary":{"prons":{"en-us":"[h\u0259\u02c8lo]","en":"[\u02c8he\u02c8l\u0259u]"},"explanations":["int.\u5582;\u54c8\u7f57","n.\u5f15\u4eba\u6ce8\u610f\u7684\u547c\u58f0","v.\u5411\u4eba\u547c(\u5582)"],"synonym":["greetings","salutations"],"antonym":[],"wqx_example":[["say hello to","\u5411\u67d0\u4eba\u95ee\u5019,\u548c\u67d0\u4eba\u6253\u62db\u547c"],["Say hello to him for me . ","\u4ee3\u6211\u95ee\u5019\u4ed6\u3002"]],"entry":"hello","type":"word","related":[],"source":"wenquxing"}}

这里不需要过多处理,点转换-嵌套:生成单个结构体 转化后

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

v3

package main

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

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 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)
   }
   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)
   }
   var dictResponse DictResponse
   //反序列化
   err = json.Unmarshal(bodyText, &dictResponse)
   if err != nil {
      log.Fatal(err)
   }
   fmt.Printf("%#v\n", dictResponse)
}

v4 只打印responce需要的部分

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代理服务器

是明文协议,不能用来翻墙 相当于在防火墙内部开了个口子,可以让用户通过单个端口访问内部所有资源 翻墙软件通常也会暴露出socks5端口给浏览器使用

爬虫爬取时很容易遇到由于IP访问频率超过限制报错,通过代理IP池解决,很多代理IP协议就是socks5协议

原理: 前端和SOCKS5之间通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。SOCKS5服务器在将通讯请求发送给真正服务器的过程中,对于请求数据包本身不加任何改变。SOCKS5服务器接收到真正服务器的响应后,也原样转发给前端。由于SOCKS作用在会话层上,因此它是一个提供会话层到会话层间安全服务的方案,不受高层应用程序变更的影响。

image.png

SOCKS服务端缺省侦听在 TCP 1080 端口。

curl是一个非常实用的、用来与服务器之间传输数据的工具

image.png

v1 TCP echo Server 发送什么就回传什么

windows环境下netcat的安装及使用_是半半呀~的博客-CSDN博客_netcat安装 windows 并将解压地址添加到Path中

image.png

image.png

package main

import (
   "bufio"
   "log"
   "net"
)

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
      }
      //写入字节,正常是写入slice,这里做一个类型强制转换
      _, err = conn.Write([]byte{b})
      if err != nil {
         break
      }
   }
}

v2 实现协议的认证阶段

image.png

package main

import (
   "bufio"
   "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
   }
   log.Println("auth success")
}
//只读流,原始的tcp连接
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
   //step1.浏览器会给代理服务器发送以下字段
   // +----+----------+----------+
   // |VER | NMETHODS | METHODS  |
   // +----+----------+----------+
   // | 1  |    1     | 1 to 255 |
   // +----+----------+----------+
   // a.VER: 协议版本,socks5为0x05
   // b.NMETHODS: 支持认证的方法数量
   // c.METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
   // X’00’ NO AUTHENTICATION REQUIRED 不需要鉴定
   // X’02’ USERNAME/PASSWORD 需要用户名密码进行鉴定

  //前两个字段都是单字节,可以用.ReadByte()读取
   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缓冲区
   method := make([]byte, methodSize)
   //用io.ReadFull将其填充
   _, 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
}

v3请求阶段

读取浏览器发来的报文,携带了用户需要访问的的URL/ip+端口号 先打印出来

image.png

package main

import (
   "bufio"
   "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])

   log.Println("dial", addr, port)
   
   //responce
   // +----+-----+-------+------+----------+----------+
   // |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
}

v4 relay

image.png

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)
   }
   //创建一个context池
   //等待任何一个方向的copy失败,就需要关闭双方连接,清零数值
   ctx, cancel := context.WithCancel(context.Background())
   defer cancel()

   go func() {
      _, _ = io.Copy(dest, reader)
      cancel()
   }()
   go func() {
      _, _ = io.Copy(conn, dest)
      cancel()
   }()
//down执行时机是cancle被调用的时机
   <-ctx.Done()
   return nil
}

image.png copy实现单向数据转换

也可以通过SwitchyOmega image.png

####作业 image.png

Q&A 1.按行读取 scanf不太可控,所以建议一般按行读取,再解析

2.goland 跳转到struct ctrl+N

3.在写代码时可以不先考虑性能优化,之后再对热点代码进行优化 字符串拼接stringBuilder相对于+可以避免内存重分配

4.TCP协议能保证HTTP知道消息的边界,HTTP协议通过换行符分割,如果body没有指定长度,就会指定分隔符,用分隔符分割

5.作业中实现两个并行的单词搜索goroutine输出两个结果更好,结果更丰富