Go语言基础学习 | 青训营笔记

141 阅读6分钟

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

1. Go语言简介

1.1. 什么是Go语言

  1. 高性能、高并发(性能媲美C++、Java
  2. 语法简单、学习曲线平缓(比如仅需For循环,上手容易)
  3. 丰富的标准库(使用标准库及其第三方库即可实现高并发、模块化支持和基础功能开发——稳定性、兼容性
  4. 完善的工具链(拥有编译、代码格式化、错误检查、帮助文档、包管理等)
  5. 静态链接(只需要编译之后的可执行文件即可部署运行,不像C++要.so,Java要JRE)
  6. 快速编译(微服务线上部署时间小于1分钟)
  7. 跨平台(支持LINUX、Windows、MacOS,无需考虑交叉编译)
  8. 垃圾回收(RuntimeGC)

1.2. Go语言大厂普及率

字节跳动、Google、Facebook、BAT、Bilibili

业务范围包括区块链、微服务、云原生

1.3. 全面拥抱Go原因

性能、性能、性能!!!

适合在线web、RPC和Http框架业务

2. Go入门

2.1. 开发环境

  • 安装Goland

    1. 访问 go.dev/ ,点击 Download ,下载对应平台安装包,安装即可
    2. 如果无法访问上述网址,可以改为访问 studygolang.com/dl 下载安装
    3. 如果访问 github 速度比较慢,建议配置 go mod proxy,参考 goproxy.cn/ 里面的描述配置,下载第三方依赖包的速度可以大大加快
  • 配置 Go 语言开发环境

    安装 VS Code (免费) 或者 Goland (收费),对于 VS Code,需要安装 Go 插件

2.2. 基础语法

  • Hello World

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println("hello world") // 打印
    }
    
    • 运行

      go run path/to/main.go
      
    • 编译

      go build path/to/main.go
      
  • 变量 var & 常量 const

    package main
    
    import (
    	"fmt"
    	"math"
    )
    
    func main() {
    
    	var a = "initial" // var name string = "a76yyyy"  // 显式初始化
    
    	var b, c int = 1, 2
    
    	var d = true
    
    	var e float64
    
    	f := float32(e) // 海象式声明
    
    	g := a + "foo"
    	fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
    	fmt.Println(g)                // initialapple
    
    	const s string = "constant" // 常量 可以根据上下文自动确定类型
    	const h = 500000000
    	const i = 3e20 / h
    	fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
    }
    
  • 流程控制

    • if else

      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")
      	}
      }
      
    • for 循环

      package main
      
      import "fmt"
      
      func main() {
      
      	i := 1
      	for { // 死循环 
      		fmt.Println("loop")
      		break
      	}
      	for j := 7; j < 9; j++ { // 经典c式循环
      		fmt.Println(j)
      	}
      
      	for n := 0; n < 5; n++ {
      		if n%2 == 0 {
      			continue //继续或 break 跳出
      		}
      		fmt.Println(n)
      	}
      	for i <= 3 {
      		fmt.Println(i)
      		i = i + 1
      	}
      }
      
    • switch 分支

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func main() {
      
      	a := 2
      	switch a { // 变量不需要括号
      	case 1:
      		fmt.Println("one")
      	case 2:
      		fmt.Println("two")
      	case 3:
      		fmt.Println("three")
      	case 4, 5:
      		fmt.Println("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")
      	}
      }
      
  • 数组与切片

    • 数组 array

      package main
      
      import "fmt"
      
      func main() {
      
      	var a [5]int //存放5元素的int a数组
      	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 [2][3]int
      	for i := 0; i < 2; i++ {
      		for j := 0; j < 3; j++ {
      			twoD[i][j] = i + j
      		}
      	}
      	fmt.Println("2d: ", twoD)
      }
      
    • 切片 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") // 扩容并返回新的slice
      	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]
      }
      
  • map 字典

    package main
    
    import "fmt"
    
    func main() {
    	m := make(map[string]int)
    	m["one"] = 1 // 写入
    	m["two"] = 2
    	fmt.Println(m)           // map[one:1 two:2]
    	fmt.Println(len(m))      // 2
    	fmt.Println(m["one"])    // 1 // 读取
    	fmt.Println(m["unknow"]) // 0
    
    	r, ok := m["unknow"] // 获取map是否包含该key
    	fmt.Println(r, ok) // 0 false
    
    	delete(m, "one") //删除
    
    	m2 := map[string]int{"one": 1, "two": 2}
    	var m3 = map[string]int{"one": 1, "two": 2}
    	fmt.Println(m2, m3)
    }
    
  • 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) // index: 0 num: 2
    		}
    	}
    	fmt.Println(sum) // 9
    
    	m := map[string]string{"a": "A", "b": "B"}
    	for k, v := range m {
    		fmt.Println(k, v) // b 8; a A
    	}
    	for k := range m {
    		fmt.Println("key", k) // key a; key b
    	}
    }
    
  • 函数 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) // 3
    
    	v, ok := exists(map[string]string{"a": "A"}, "a")
    	fmt.Println(v, ok) // A True
    }
    
  • 指针 point

    package main
    
    import "fmt"
    
    func add2(n int) {
    	n += 2 // 无效操作
    }
    
    func add2ptr(n *int) {
    	*n += 2 // 指针操作 * 取值
    }
    
    func main() {
    	n := 5
    	add2(n)
    	fmt.Println(n) // 5
    	add2ptr(&n) // & 取地址
    	fmt.Println(n) // 7
    }
    
  • 结构体及方法 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
      }
      
    • 结构体方法

      package main
      
      import "fmt"
      
      type user struct {
      	name     string
      	password string
      }
      
      func (u user) checkPassword(password string) bool { //实现 类Class 的方法
      	return u.password == password
      }
      
      func (u *user) resetPassword(password string) {
      	u.password = password
      }
      
      func main() {
      	a := user{name: "wang", password: "1024"}
      	a.resetPassword("2048")
      	fmt.Println(a.checkPassword("2048")) // true
      }
      
  • 错误处理

    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() {
    	u, err := findUser([]user{{"wang", "1024"}}, "wang")
    	if err != nil { // 判断Error是否存在
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(u.name) // wang // 否则可能出现空指针错误
    
    	if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
    		fmt.Println(err) // not found
    		return
    	} else {
    		fmt.Println(u.name)
    	}
    }
    
  • 字符串 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
      }
      
    • 字符串格式化 format

      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
      }
      
  • 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"}}
    	buf, err := json.Marshal(a) // 序列化
    	if err != nil { 
    		panic(err)
    	}
    	fmt.Println(buf)         // [123 34 78 97...]
    	fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
    
    	buf, err = json.MarshalIndent(a, "", "\t")
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(string(buf))
    
    	var b userInfo
    	err = json.Unmarshal(buf, &b) // 反序列化
    	if err != nil {
    		panic(err)
    	}
    	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
    }
    
  • 时间处理

    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 // 获取时间戳
    }
    
  • 数字解析 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
    }
    
  • 进程信息

    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
    }
    

3.实战

3.1. 猜谜游戏

  • 介绍

    • 每轮游戏会从 1 到 n 随机选择一个数字,猜选出的是哪个数字。
    • 如果猜错了,会告诉猜测的数字比选出的数字是大还是小了。
  • Code

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"math/rand"
    	"os"
    	"strconv"
    	"strings"
    	"time"
    )
    
    func main() {
    	maxNum := 100
    	rand.Seed(time.Now().UnixNano()) // 使用rand 需要设置随机数种子
    	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, "\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 // 猜谜正确后退出循环
    		}
    	}
    }
    

3.2 在线命令行词典

  • 介绍和操作

  • Code

    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() // 函数结束后从下往上触发,结束Body流
    	bodyText, err := ioutil.ReadAll(resp.Body) // 读取响应
    	if err != nil {
    		log.Fatal(err)
    	}
    	if resp.StatusCode != 200 { // 防止response status_code != 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 { // 判断是否参数数量为2
    		fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
    example: simpleDict hello
    		`)
    		os.Exit(1)
    	}
    	word := os.Args[1]
    	query(word)
    }
    

3.3 SOCKS5代理

  • 介绍

  • Code

    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
    }
    
  • 总结

    本节课主要是学习了GO语言的一些基础语法和一些代码层面的实战,包括控制台输入字节流的读取函数逻辑处理、Http查询API的爬虫**JSON 序列和反序列化操作、以及 **Socks5 代理的GO实现等。第一节课的知识量还是很大的,没有一定的go基础的话,可能得下来再继续读一些Go入门的书籍和代码巩固一下。