Go语言基础入门 | 青训营笔记

53 阅读6分钟

Go语言基础入门| 青训营笔记

1. Go的特点

  1. 高性能,高并发
  2. 语法简单,学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

2. 开发环境配置

2.1. 安装 Go 语言

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

2.2. 配置 Go 语言开发环境

可以选择安装 VS Code , 或者 Goland ,对于 VS Code,需要安装 Go 插件

3. 基础语法

3.1. Hello World

main.go文件

package main
​
import (
    "fmt"
)
​
func main() {
    fmt.Println(" 你好呀!")
}

在控制台输入,即可打印

go run [文件路径]  如:go run example/01-hello/main2.go

3.2. 变量

go是一门强类型语言,使用和优先级与c和c++类似

package main
​
import (
    "fmt"
    "math"
)
​
func main() {
    //声明变量的两种形式//1. 使用var声明变量,自动匹配变量类型
    var a = "initial"var b, c int = 1, 2 //如有需要,使用这个形式申明类型var d = truevar e float64//2. 使用 := 声明变量,自动匹配变量类型
    f := float32(e)
​
    g := a + "foo"
    fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
    fmt.Println(g)                // initialapple//声明常量,go中的常量没有确定的类型,会根据上下文自动推断常量类型
    const s string = "constant"
    const h = 500000000
    const i = 3e20 / h
    fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
  • 声明变量的两种形式

    1. 使用var声明变量,自动匹配变量类型
    2. 使用 := 声明变量,自动匹配变量类型
  • 声明常量,go中的常量没有确定的类型,会根据上下文自动推断常量类型

3.3. if else

package main
​
import "fmt"
​
func main() {
​
    //1. go的条件判断没有括号
    //2. go的if后必须接大括号
    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")
    }
}
  1. go的条件判断没有括号
  2. go的if后必须接大括号

3.4. for循环

package main
​
import "fmt"func main() {
​
    //go中只有for一种循环
    i := 1
    //死循环
    for {
        fmt.Println("loop")
        break
    }
    //1. for条件同样没有括号,三个条件可以任意缺少
    for j := 7; j < 9; j++ {
        fmt.Println(j)
    }
​
    //2. 可以使用continue
    for n := 0; n < 5; n++ {
        if n%2 == 0 {
            continue
        }
        fmt.Println(n)
    }
    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }
}
  • go中只有for一种循环

    1. for条件同样没有括号,三个条件可以任意缺少
    2. 可以使用continue,break

3.5. switch

package main
​
import (
    "fmt"
    "time"
)
​
func main() {
​
    a := 2
    //1. switch条件没有括号,条件可以是任意的变量类型,如字符串,结构体等
    //2. 所有case条件句默认存在c语言中的break,也就是说执行完对应条件句后,不会继续往下执行
    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")
    }
​
    //3. switch功能强大,升值可以直接代替if-else嵌套,使代码更加清晰简洁
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }
}
  1. switch条件没有括号,条件可以是任意的变量类型,如字符串,结构体等
  2. 所有case条件句默认存在c语言中的break,也就是说执行完对应条件句后,不会继续往下执行
  3. switch功能强大,升值可以直接代替if-else嵌套,使代码更加清晰简洁

3.6. 数组

package mainimport "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 [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}
  • 由于数组长度不可变,所以业务中更常使用的是切片

3.7. slice切片

package main
​
import "fmt"func main() {
​
    //1. 使用make创建slice
    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//2. append方法可以进行自动扩容,但一定要有变量名接收
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s) // [a b c d e f]//3. 支持复制slice
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println(c) // [a b c d e f]//4. 支持根据索引截取数组,但不支持负数索引
    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]
}
  • slice可以理解为可变长度的数组

    1. 使用make创建slice
    2. append方法可以进行自动扩容,但一定要有变量名接收
    3. 支持复制slice
    4. 支持根据索引截取数组,但不支持负数索引

3.8. map

package main
​
import "fmt"func main() {
    //1. make创建map map[key类型]value类型
    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//2. 变量后加ok表示在这个map中找key为unkown的value
    r, ok := m["unknow"]
    fmt.Println(r, ok) // 0 false//3. 删除操作
    delete(m, "one")
​
    //4. 其他定义方式
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3)
}
  1. make创建map map[key类型]value类型
  2. 变量后加ok表示在这个map中找key为unkown的value

3.9. range遍历

package main
​
import "fmt"func main() {
    nums := []int{2, 3, 4}
    sum := 0
    //1. 遍历数组,i为下标,num为数组变量
    for i, num := range nums {
        sum += num
        if num == 2 {
            fmt.Println("index:", i, "num:", num) // index: 0 num: 2
        }
    }
    fmt.Println(sum) // 9//2. 遍历map,k为key,v为nums
    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
    }
}
  1. 遍历数组,i为下标,num为数组变量
  2. 遍历map,k为key,v为nums

3.10. func函数

package main
​
import "fmt"//1. 返回类型后置
func add(a int, b int) int {
    return a + b
}
​
func add2(a, b int) int {
    return a + b
}
​
//2. 支持返回多个值,业务中也常返回多个值,v是值,ok是错误信息
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
}

3.11. 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
}
​
  • go中指针的作用主要是对参数的值进行修改

3.12. struct结构体

package main
​
import "fmt"//结构体定义
type user struct {
    name     string
    password string
}
​
func main() {
    //结构体初始化的几种形式,未初始化的数字为0,字符串为空
    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
}
​

3.13. 结构体方法

package main
​
import "fmt"type user struct {
    name     string
    password string
}
​
//1. 结构体函数类似类成员方法
func (u user) checkPassword(password string) bool {
    return u.password == password
}
​
//2. 带指针可以对结构体函数进行修改
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
}
  1. 结构体函数类似类成员方法
  2. 带指针可以对结构体函数进行修改

3.14.error错误处理

package main
​
import (
    "errors"
    "fmt"
)
​
type user struct {
    name     string
    password string
}
​
//1. 返回类型添加error做异常处理
func findUser(users []user, name string) (v *user, err error) {
    for _, u := range users {
        if u.name == name {
            //正常执行返回值和nil
            return &u, nil
        }
    }
    //出现错误返回,nil和报错信息
    return nil, errors.New("not found")
}
​
func main() {
    u, err := findUser([]user{{"wang", "1024"}}, "wang")
    //判断err是否为空,若不为空,打印日志
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(u.name) // wangif u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
        fmt.Println(err) // not found
        return
    } else {
        fmt.Println(u.name)
    }
}
​

3.15. 字符串操作

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
}

3.16.时间操作

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

3.17. strconv数字操作

package main
​
import (
    "fmt"
    "strconv"
)
​
func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f) // 1.234//字符串,十进制,64位精度
    n, _ := strconv.ParseInt("111", 10, 64)
    fmt.Println(n) // 111//精度参数为0,说明自动推断转换精度
    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
}
​

3.18. 进程信息

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

4. 实战练习

4.1. 猜谜游戏

使用go语言写一个猜谜游戏代码,巩固语法

package main
​
import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
)
​
func main() {
    maxNum := 100
    //根据当前时间戳设定随机种子
    rand.Seed(time.Now().UnixNano())
    var 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 try again", err)
            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 smarller than the secret number. Please try again")
        } else {
            fmt.Println("Your guess is correct!")
            break
        }
    }
}
​

4.2. 在线词典

利用彩云翻译的api接口制作一个在线词典,练习http请求和json解析

彩云翻译:彩云小译 - 在线翻译 (caiyunapp.com)

几个方便使用的工具网站

GoHttp请求代码生成器:Convert curl commands to code (curlconverter.com)

json解析结构体生成:JSON转Golang Struct - 在线工具 - OKTools

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 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)
}
​
func query(word string) {
    client := &http.Client{}
    request := DictRequest{TransType: "en2zh", Source: word}
​
    //序列化json
    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("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("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="100", "Microsoft Edge";v="100"`)
    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/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36")
    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)
    }
    if resp.StatusCode != 200 {
        log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
    }
​
    //反序列化json
    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)
    }
}

4.3. Socks5代理

socks5代理原理图

image-20230117121000198

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 = 0x04func 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.WithCancel启动并行,等待cancel()指令
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() //防御式编程,没什么作用//两个方法并行启动,形成双向数据传输
    go func() {
        _, _ = io.Copy(dest, reader)
        cancel()
    }()
    go func() {
        _, _ = io.Copy(conn, dest)
        cancel()
    }()
​
    //cancel后结束
    <-ctx.Done()
    return nil
}

代理运行效果: