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

156 阅读8分钟

青训营第一次课学习笔记

Go语言基础知识学习笔记

什么是go语言

  • 高性能
  • 语法简单、学习曲线平缓
  • 丰富的标准库
  • 完善的工具链
  • 静态链接
  • 快速编译
  • 跨平台
  • 垃圾回收

安装和环境配置

安装

  • 下载go然后解压
  • PATH配置为 go/bin/

环境变量

  • GOROOT:表示Go的安装目录
  • GOPATH:表示工作空间,第三方工具会安装在这个目录下

vscode的go插件报错提示工具链安装失败的解决:

  • 执行:go env -w GOPROXY=https://goproxy.cn,direct

基础语法

这部分内容因为看一眼就会了,笔记的话主要以注释为主,对在其他语言中没见过的特性解释一下。

hello world

go.mod文件

module main //指定当前这个模块的名字为main 
go 1.20 //go 版本为1.2

main.go文件

package main // 表示该文件属于名为main的包的一部分

import (
	"fmt" // 导入标准库的format包
)

func main()  {
	fmt.Println("hello world!")
	// 运行: go run ./helloworld.go
	// 编译: go build ./helloworld.go
}

类型

var 是变量 const是常量 都可以自动推断类型

package main

import (
	"fmt"
	"math"
)

func main() {
	// var 是变量 const是常量 都可以自动推断类型
	var a = "aaaa"      // 字符串 自动推导类型
	var b, c int = 1, 2 // 数值
	var d = true
	var e float64 = 0.5
	f := float64(e) // := 可以实现不声明f而直接赋值
	g := a + "bbb"
	const pi float64 = 3.14    // 常量声明
	fmt.Println(a, b, c, d, e) // aaaa 1 2 true 0.5
	fmt.Println(f)             // 0.5
	fmt.Println(g)             // aaaabbb
	fmt.Println(math.Cos(pi))  // -0.9999987317275396
}

if

if 不用写() 但必须写{}

package main

import (
	"fmt"
)

func main() {
	// if 不用写() 但必须写{}
	if 1+1 == 2 {
		fmt.Println("1+1=2") // 1+1=2
	} else {
		fmt.Println("1+1!=2")
	}

	if flag := true; flag == true {
		fmt.Println("flag==true")
	}
}

for

package main

import (
	"fmt"
)

func main() {
	// go没有while循环
	for {
		fmt.Println("这是死循环")
		break // 跳出循环
	}
	for i := 0; i < 10; i++ { // 经典for循环,可以其中三个语句都可以省略
		fmt.Println(i)
		continue // 进入下一个循环
	}
	i := 0
	for i < 10 { // 退化为while循环
		fmt.Println(i)
		i++
	}
}

switch

package main

import (
	"fmt"
	"time"
)

func main() {
	// switch会自动break!!!
	i := 1
	switch i {
	case 1:
		fmt.Println("1")
	case 2:
		fmt.Println("2")
	default:
		fmt.Println("default")
	}

	t := time.Now()
	// Switch支持任意类型,字符串、结构体
	switch {
	case t.Hour() < 12:
		fmt.Println("现在是上午")
	default:
		fmt.Println("现在是下午")
	}
}

数组

package main

import (
	"fmt"
)

func main() {
	var a [5]int
	a[0] = 1
	fmt.Println(a[0])

	var b [5][5]int
	b[0][0] = 1
	fmt.Println(b[0][0])

	c := [3]int{1, 2, 3} // c是数组
	fmt.Println(c)       // [1 2 3]
	// c=append(c,4) // 报错,c不是切片,是数组
}

切片

package main

import "fmt"

func main() {
	a := make([]int, 4)
	a[0] = 1
	a[1] = 2
	a[2] = 3
	a = append(a, 5) // 必须把结果赋值
	fmt.Println(a)   // [1 2 3 0 5]

	// 和python类似,支持切片操作但不支持负数
	fmt.Println(a[1:3]) // [2 3]
	fmt.Println(a[1:])  // [2 3 0 5]
	fmt.Println(a[:3])  // [1 2 3]

	b := make([]int, len(a))
	copy(b, a)
	fmt.Println(b) //[1 2 3 0 5]

	c := []int{1, 2, 3} // c是切片
	c = append(c, 4)
	fmt.Println(c[0:4]) // [1 2 3 4]

	var d []int // d 是切片
	d = append(d, 1)
	fmt.Println(d) // [1]
}

map

package main

import "fmt"

func main() {
	// 类似于其他语言的hash、字典
	// map是无序的
	a := make(map[string]int)
	a["A"] = 10
	fmt.Println(a)
	fmt.Println(a["A"]) //map[A:10]
	fmt.Println(len(a)) // 1
	val, exist := a["A"]
	if exist {
		fmt.Println(val) // 10
		delete(a, "A")
	}
	b := map[string]int{"B": 20}
	fmt.Println(b) // map[B:20]
}

range

package main

import "fmt"

func main() {
	// range可以遍历slice和map

	a := []int{1, 2, 3}
	for idx, val := range a {
		fmt.Println(idx, val)
	}

	b := map[string]int{"aaa": 1, "bbb": 2}
	for key, val := range b {
		fmt.Println(key, val)
	}
}

函数

package main

import "fmt"

func divsionA(a int, b int) int {
	return a / b
}

// 可以省略类型
func divsionB(a, b int) int {
	return a / b
}

// 可以返回多个值
func divsionC(a, b int) (res int, ok bool) {
	if b != 0 {
		res = a / b
		ok = true
	}
	return res, ok
}

func main() {
	fmt.Println(divsionA(4, 2)) // 2
	fmt.Println(divsionB(4, 2)) // 2
	fmt.Println(divsionC(4, 0)) // 0 false
}

指针

package main

import "fmt"

func add(n int) {
	n++
}
func padd(n *int) {
	*n += 1
}

func main() {
	n := 1
	add(n)
	fmt.Println(n)
	padd(&n)
	fmt.Println(n)
}

结构体

package main

import "fmt"

type user struct {
	uuid string
	name string
	pswd string
}

func setPswd(u *user, pswd string) {
	u.pswd = pswd
}
func checkPswd(u user, pswd string) bool {
	return u.pswd == pswd
}

func main() {
	a := user{uuid: "001", name: "dyg", pswd: "111"}
	b := user{"001", "dyg", "111"}
	fmt.Println(a)
	fmt.Println(b)

	setPswd(&a, "123")
	fmt.Println(checkPswd(a, "123"))
}

结构体方法

package main

import "fmt"

type user struct {
	uuid string
	name string
	pswd string
}

// 带指针才能对结构体修改
func (u *user) setPswd(pswd string) {
	u.pswd = pswd
}
func (u user) checkPswd(pswd string) bool {
	return u.pswd == pswd
}

func main() {

	a := user{uuid: "001", name: "dyg", pswd: "111"}
	a.setPswd("222")
	fmt.Println(a.checkPswd("222")) // true
}

错误处理

package main

import (
	"errors"
	"fmt"
)

type user struct {
	uuid string
	name string
	pswd string
}

func findUser(users []user, name string) (res *user, err error) {
	for _, usr := range users { // _可以用来占位,这样不会报错:"变量声明但未使用"
		if usr.name == name {
			return &usr, nil
		}
	}
	return nil, errors.New("not fond")
}

func main() {
	users := []user{{name: "dyg"}}

	user, err := findUser(users, "hhh")
	if err != err {
		fmt.Println(user)
	} else {
		fmt.Println(err)
	}
}

字符串的工具函数

package main

import (
	"fmt"
	"strings"
)

func main() {
	a := "123"
	fmt.Println(strings.Contains(a, "12"))                 //true
	fmt.Println(strings.Count(a, "1"))                     //1
	fmt.Println(strings.HasPrefix(a, "1"))                 //1
	fmt.Println(strings.HasSuffix(a, "23"))                //true
	fmt.Println(strings.Index(a, "23"))                    //true
	fmt.Println(strings.Join([]string{"aaa", "bbb"}, "-")) //aaa-bbb
	fmt.Println(strings.Repeat(a, 2))                      //123123
	fmt.Println(strings.Replace(a, "23", "00", -1))        //100
	fmt.Println(strings.ReplaceAll(a, "23", "00"))         //100
	fmt.Println(strings.Split("a-b-c-d", "-"))             //[a b c d]
	fmt.Println(strings.ToLower(a))                        //123
	fmt.Println(strings.ToUpper(a))                        //123
	fmt.Println(len(a))                                    //3
	fmt.Println(len("中文"))                                 //6
}

格式化输出

package main

import (
	"fmt"
)

type point struct {
	x int
	y int
}

func main() {
	fmt.Printf("a=%.3f\n", 3.1415)
	a := point{1, 2}
	fmt.Printf("a=%v\n", a)  // %v 可以输出任何类型
	fmt.Printf("a=%+v\n", a) // %+v 输出字段详细信息
	fmt.Printf("a=%#v\n", a) // %#v 输出类型详细信息
	// 输出:
	//a=3.142
	//a={1 2}
	//a={x:1 y:2}
	//a=main.point{x:1, y:2}
}

json

package main

import (
	"encoding/json"
	"fmt"
)

type user struct {
	// 字段名称为大写为公开字段,json可对这些字段序列化
	Name string
	pswd string
	Age  int `json:"age"` // 可以自定义json字段名
}

func main() {
	u1 := user{"dyg", "pswd", 10}
	buf, _ := json.Marshal(u1)
	fmt.Println(string(buf)) //{"Name":"dyg","age":10}
	var u2 user
	err := json.Unmarshal(buf, &u2)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%+v\n", u2) // {Name:dyg pswd: Age:10}

}

时间

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	fmt.Println(now) // 2023-07-25 16:13:24.4809153 +0800 CST m=+0.007581401

	t1 := time.Date(2023, 7, 25, 16, 05, 31, 0, time.UTC)
	fmt.Println(t1)                                                                                    //2023-07-25 16:05:31 +0000 UTC
	fmt.Println(t1.Year(), t1.Month(), t1.Day(), t1.Hour(), t1.Minute(), t1.Second(), t1.Nanosecond()) //2023 July 25 16 5 31 0

	t2 := time.Date(2023, 7, 25, 16, 10, 31, 0, time.UTC)
	fmt.Println(t2.Format("2006-01-02 15:04:05")) // 2023-07-25 16:10:31

	differ := t2.Sub(t1)
	fmt.Println(differ)                             // 5m0s
	fmt.Println(differ.Minutes(), differ.Seconds()) //5 300

	t3, err := time.Parse("2006-01-02 15:04:05", "2023-07-25 16:05:31")
	fmt.Println(t3, err) // 2023-07-25 16:05:31 +0000 UTC <nil>
}

数字解析

package main

import (
	"fmt"
	"strconv"
)

func main() {
	num1, err := strconv.ParseInt("123", 10, 64) // 123 10进制 返回64bit精度数值
	fmt.Println(num1, err)                       //123 <nil>

	num2, err := strconv.ParseInt("ff", 16, 64) // 16进制不能写0x 否则会报错
	fmt.Println(num2, err)                      //255 <nil>

	num3, err := strconv.ParseInt("0xff", 0, 64) // 0进制表示自动根据前缀判断进制
	fmt.Println(num3, err)                       //255 <nil>

	num4, err := strconv.Atoi("123") // 10进制转换
	fmt.Println(num4, err)           //123 <nil>

	num5_str := strconv.Itoa(123) // 10进制转换
	fmt.Println(num5_str)         //123
}

进程信息

package main

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

func main() {
	fmt.Println(os.Args) // 参数列表

	// 读取和设置环境
	fmt.Println(os.Getenv("GOROOT")) // D:\app\go1.20.6.windows-amd64\go
	fmt.Println(os.Getenv("GOPATH")) // D:\app\go1.20.6.windows-amd64\gopath
	err := os.Setenv("AAA", "BBB")
	fmt.Println(err) // <nil>

	// 执行子进程并获取输出
	res, err := exec.Command("go", "version").CombinedOutput()
	fmt.Println(string(res), err) // go version go1.20.6 windows/amd64   <nil>
}

小项目实践

猜数字实现

package main

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

func main() {
	r := rand.New(rand.NewSource(time.Now().UnixMilli())) // 用时间戳创建随机种子源,用随机源创建rand实例对象
	res := r.Intn(100)
	fmt.Println("随机数已生成,快来猜一猜!")
	bufReader := bufio.NewReader(os.Stdin)
	for {
		str, err := bufReader.ReadString('\n')
		if err != nil {
			fmt.Println("输入有误,请重新输入,err:", str, err)
			continue
		}
		str = strings.TrimSpace(str)
		num, err := strconv.Atoi(str)
		if err != nil {
			fmt.Println("输入有误,请重新输入,err:", err)
			continue
		}
		if num < res {
			fmt.Println("猜小了!")
		} else if num > res {
			fmt.Println("猜大了!")
		} else {
			fmt.Println("猜对了!")
			break
		}
	}
}

在线字典实现

好用的工具:

package main

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

func main() {
	if len(os.Args) == 1 {
		fmt.Println("usage:go run ./main.go [word]")
		return
	}
	qwery(os.Args[1])
	/*
		运行结果:
		> go run .\main.go hello
		int.喂;哈罗
		n.引人注意的呼声
		v.向人呼(喂)
	*/
}

func qwery(word string) {
	directReq := reqBody{TransType: "en2zh", Source: word}
	buf, err := json.Marshal(directReq)
	client := &http.Client{}
	if err != nil {
		log.Fatal(err)
	}
	var data = strings.NewReader(string(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")
	req.Header.Set("app-name", "xy")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "1b1672e7e5e580803a8e002f594e0306")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("sec-ch-ua", `"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"`)
	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/114.0.0.0 Safari/537.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 := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	var res resBody
	json.Unmarshal(bodyText, &res)
	for _, exp := range res.Dictionary.Explanations {
		fmt.Println(exp)
	}
}

type reqBody struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
}
type resBody struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `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      []interface{} `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"`
}

socket5代理服务器实现

使用curl测试 curl --socks5 代理服务器地址:端口 访问地址 -v

socket5原理

  • 先是客户端(如浏览器)和代理服务建立连接
  • 然后是代理服务器和待访问地址服务器建立连接
  • 然后才是发送数据和响应数据的阶段 转存失败,建议直接上传图片文件

简易echoServer实现

package main

import (
	"bufio"
	"fmt"
	"net"
)

func main() {
	listener, err := net.Listen("tcp", "0.0.0.0:1080") // 创建服务器
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("tcp server is running at 0.0.0.0:1080")
	for {
		connection, err := listener.Accept() // 接受到客户端请求
		if err != nil {
			fmt.Println(err)
			continue
		}
		go process(connection) // 处理客户端请求
	}
}
func process(conn net.Conn) {
	defer conn.Close()                 // 在函数执行完毕后调用关闭连接
	bufReader := bufio.NewReader(conn) // 创建缓冲读取流
	for {
		bit, err := bufReader.ReadByte() // 读取一个字节的数据
		if err != nil {
			fmt.Println(err)
			return
		}
		_, err = conn.Write([]byte{bit}) // 发送一个字节的数据
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

socket5代理服务器实现

package main

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

	"golang.org/x/exp/slices"
)

func main() {
	listener, err := net.Listen("tcp", "0.0.0.0:1080") // 创建服务器
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("tcp server is running at 0.0.0.0:1080")
	for {
		connection, err := listener.Accept() // 接受到客户端请求
		if err != nil {
			fmt.Println(err)
			continue
		}
		go process(connection) // 处理客户端请求
	}
}

const VersionCode_Socket5 = 0x05
const MethodsCode_Not_Require_Authority = 0x00
const CmdCode_Connect = 0x01
const AddrTypeCode_ipv4 = 0x01
const AddrTypeCode_domain = 0x03
const AddrTypeCode_ipv6 = 0x04

func authority(reader *bufio.Reader, conn net.Conn) (err error) {
	// 1.这里实际上就是浏览器将告诉代理服务器它支持的协议版本和鉴权方式
	// 浏览器发来的报文格式:
	// +-------+-----------+------------+
	// | ver   | N-method  | methods 	|
	// +-------+-----------+------------+
	// | 1Byte | 1Byte     | 1-255Bytes |
	// +-------+-----------+------------+
	// version:协议版本,socket5为0x05
	// N-method 鉴权方式的个数
	// methods 含支持的每个鉴权方式的编码
	// - 0x00 不需要鉴权
	// - 0x02 需要用户名密码鉴权
	version, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read version fail,error:%v", err)
	}
	if version != VersionCode_Socket5 {
		return fmt.Errorf("unsupport version:%v", version)
	}
	n, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read n fail error:%v", err)
	}
	methods := make([]byte, n)            // 创建一个长度为n的切片
	_, err = io.ReadFull(reader, methods) // 填充切片
	if err != nil {
		return fmt.Errorf("read methods fail error:%v", err)
	}
	if !slices.Contains(methods, MethodsCode_Not_Require_Authority) { // 如果浏览器支持的鉴权方式中没有服务器所需的方式
		return fmt.Errorf("unsupport method:%v", MethodsCode_Not_Require_Authority)
	}
	log.Println("version:", version, "methods:", methods)

	// 2.现在,代理服务器要告诉浏览器选择了什么协议
	// 发给浏览器的报文格式:
	// +-------+------------+
	// | ver   | method 	|
	// +-------+------------+
	// | 1Byte | 1Byte      |
	// +-------+------------+
	_, err = conn.Write([]byte{VersionCode_Socket5, MethodsCode_Not_Require_Authority})
	if err != nil {
		return fmt.Errorf("write fail error:%v", err)
	}
	return nil
}
func connect(bufReader *bufio.Reader, clientConn net.Conn) (err error) {
	// 浏览器会发来报文信息,含要访问的目标地址信息,代理服务器要通过报文信息和目标地址建立连接,报文格式:
	// +-----+-----+------+------+-----------+-----------+
	// | VER | CMD | RSV  | ATYP | Dist.Addr | Dist.Port |
	// +-----+-----+------+------+-----------+-----------+
	// |  1  |  1  | 0x00 |  1   | varable   |    2      |
	// +-----+-----+------+------+-----------+-----------+
	// Ver 版本号 0x05 为socket5
	// CMD 命令  0x01 表示连接请求
	// Atyp 目标地址类型:
	// - 0x01 	ipv4地址  Dist.Addr为四字节
	// - 0x03   域名      Dist.Addr为可变长值
	// Dist.Addr 目标地址,可变长值
	// Dist.Port 目标端口,2字节
	buf := make([]byte, 4)
	_, err = io.ReadFull(bufReader, buf)
	if err != nil {
		return fmt.Errorf("read dialgram fail: %v", err)
	}
	ver, cmd, atyp := buf[0], buf[1], buf[3]
	if ver != VersionCode_Socket5 {
		return fmt.Errorf("unsupport version: %v", ver)
	}
	if cmd != CmdCode_Connect {
		return fmt.Errorf("unsupport cmd: %v", cmd)
	}
	addr := ""
	switch atyp {
	case AddrTypeCode_ipv4:
		_, err := io.ReadFull(bufReader, buf)
		if err != nil {
			return fmt.Errorf("read addr fail: %v", err)
		}
		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
	case AddrTypeCode_domain:
		n, err := bufReader.ReadByte()
		if err != nil {
			return fmt.Errorf("read domain fail: %v", err)
		}
		domain := make([]byte, n)
		_, err = io.ReadFull(bufReader, domain)
		if err != nil {
			return fmt.Errorf("read domain fail: %v", err)
		}
		addr = string(domain)
	case AddrTypeCode_ipv6:
		return fmt.Errorf("unsupport ipv6")
	default:
		return fmt.Errorf("unknow ATYP")
	}
	_, err = io.ReadFull(bufReader, buf[:2]) // 只读取两个字节,cun到buf中
	if err != nil {
		return fmt.Errorf("read port fail:%v", err)
	}
	port := binary.BigEndian.Uint16(buf[:2])
	fmt.Println("dial:", addr, port)

	// 现在通过浏览器发来的报文,得到了浏览器想要访问的目标主机的地址。
	// 现在,让代理服务器和和目标主机建立TCP连接
	destConn, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
	if err != nil {
		return fmt.Errorf("dial fail:%v", err)
	}
	defer destConn.Close()
	fmt.Println("dial success")

	// 现在给浏览器一个响应,让浏览器发实际的请求,报文格式:
	// +-----+-----+------+------+-----------+-----------+
	// | VER | REP | RSV  | ATYP | Dist.Addr | Dist.Port |
	// +-----+-----+------+------+-----------+-----------+
	// |  1  |  1  | 0x00 |  1   | varable   |    2      |
	// +-----+-----+------+------+-----------+-----------+
	// VER socks版本 这里为0x05
	// REP Relay Field 填0x00 表示成功
	// RSV 保留字段
	// Atyp 目标地址类型:
	// - 0x01 	ipv4地址  Dist.Addr为四字节
	// - 0x03   域名      Dist.Addr为可变长值
	// Dist.Addr 目标地址,可变长值
	// Dist.Port 目标端口,2字节
	_, err = clientConn.Write([]byte{VersionCode_Socket5, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) // socks5协议不需要填地址,所以填0
	if err != nil {
		return fmt.Errorf("write fail:%v", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	// 这里相当于是开了两个子进程
	go func() {
		// copy实现是用死循环把 "源"拷贝到"目标"
		io.Copy(destConn, clientConn) // 把浏览器的请求数据发给目标主机
		cancel()                      // 出错后将导致cancel被调用
	}()
	go func() {
		io.Copy(clientConn, destConn) // 把目标主机的响应发给浏览器
		cancel()
	}()
	<-ctx.Done() // 等待ctx被执行完成,也就是cancel()被调用,否则会直接return,导致建立的连接被断开
	return nil
}
func process(conn net.Conn) {
	defer conn.Close()                 // 在函数执行完毕后调用关闭连接
	bufReader := bufio.NewReader(conn) // 创建缓冲读取流
	err := authority(bufReader, conn)  // 鉴权
	if err != nil {
		log.Println("client %v,authorty fail:%v", conn.RemoteAddr(), err)
	}
	err = connect(bufReader, conn)
	if err != nil {
		log.Println("client %v,connect fail:%v", conn.RemoteAddr(), err)
	}
}

启动测试结果

> go run .\main.go
tcp server is running at 0.0.0.0:1080
2023/07/26 00:32:57 version: 5 methods: [0 1]
dial: 36.152.44.96 80
dial success
> curl --socks5 172.30.64.1:1080 http://www.baidu.com -v
*   Trying 172.30.64.1:1080...
* SOCKS5 connect to IPv4 36.152.44.96:80 (locally resolved)
* SOCKS5 request granted.
* Connected to (nil) (172.30.64.1) port 1080 (#0)
> GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Tue, 25 Jul 2023 16:33:00 GMT
< Etag: "588604f0-94d"
< Last-Modified: Mon, 23 Jan 2017 13:28:16 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
* Connection #0 to host (nil) left intact