Go语法入门 + 项目实战

👂 Take me Hand Acoustic - Cécile Corbel - 单曲 - 网易云音乐

第3个小项目有问题,不能在Windows下跑,懒得去搜Linux上怎么跑了,已经落下进度了....

目录

😳前言

🍉Go两小时

🔑小项目实战

🐘猜数字游戏

🐘在线词典

[代码1 -- 抓包](#代码1 -- 抓包)

[代码2 -- 生成request body](#代码2 -- 生成request body)

[代码3 -- 解析 response body](#代码3 -- 解析 response body)

[代码4 -- 打印结果](#代码4 -- 打印结果)

[代码6 -- 完整代码](#代码6 -- 完整代码)

cmd测试

🐘Socks5代理服务器

🔑介绍

📚原理

[💻TCP echo server](#💻TCP echo server)

🚢auth

🏘请求阶段

🤪relay阶段

📚Hertz框架


😳前言

注意! run时,要点++func main()左边的++绿色▲,而不是右上方的虫子和▲

++注意2~++ 一个文件夹下,只能有一个main.go文件,比如这样👇

学Go,除了字节内部课,还需要一套B站全流程的新手入门视频,只是内部课的话,节奏比较快,虽然也是有视频事无巨细地教,但有些点小白还是不懂的,需要到B站新手视频处查阅,需要一遍又一遍bing,Google,百度,GPT,stackOverFlow,youtube,B站(一共7个工具,记好了,加上算法群,项目群,在职群....效率不会低的)

还需要很多文档...

🍉Go两小时

注意先安装Goland,Git,弄个Github学生认证,免费使用

跟着敲一遍(182行代码),先有个印象👇

从 Java 的角度初识 Go 语言 | 青训营笔记 - HikariLan's Blog (minecraft.kim)

跟着敲完180行入门代码还不过瘾的话,再跟👇这个5小时教程,巩固一下基础

【golang教学】第二章:golang的基础知识------结构,包,变量初探(1010工作室出品)_哔哩哔哩_bilibili

**代码 --**复制可运行

Go 复制代码
package main

import (
	"errors"
	"fmt"
	"time"
)

import (
	_ "errors"
)

// 错误处理
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 add(a int, b int) int {
	return a + b //函数要写在主函数外
}

func add2(n int) {
	n += 2
}

func add2ptr(n *int) {
	*n += 2
}

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() {
	var a int = 1
	var b, c int = 1, 3
	fmt.Println(a, b, c)

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

	t := 2 //声明并初始化一个新的变量
	switch t {
	case 0, 1:
		fmt.Println("zero or one")
	case 2:
		fmt.Println("two")
	default:
		fmt.Println("other")
	}

	tt := time.Now()
	switch {
	case tt.Hour() < 12:
		fmt.Println("it is before noon")
	default:
		fmt.Println("it is after noon")
	}

	v := 42
	switch v {
	case 100:
		fmt.Println(100)
		fallthrough
	case 42:
		fmt.Println(42)
		fallthrough
	case 1:
		fmt.Println(1)
		fallthrough
	default:
		fmt.Println("default")
	}

	for j := 7; j < 9; j++ {
		fmt.Println(j)
	}

	i := 1
	for i <= 3 {
		fmt.Println(i)
		i = i + 1
	}

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

	//遍历map, 得到 键 k 值 v
	m := make(map[string]int)
	m["hello"] = 0
	m["world"] = 1
	//if key and value both needed
	for k, v := range m {
		fmt.Printf("%v %v\n", k, v)
	}
	//or only need key
	for k := range m {
		fmt.Printf("%v \n", k)
	}

	var aa [5]int
	for i := 0; i < 3; i++ {
		aa[i] = i
		fmt.Println(aa[i])
	}
	bb := [5]int{1, 2, 3, 4, 5}
	fmt.Println(bb[4])

	var twoD [2][3]int
	fmt.Println(twoD[1][2])

	//切片
	s := make([]string, 3, 10)
	s[0] = "a"
	s[1] = "b"
	s[2] = "c"
	for i := 0; i < 3; i++ {
		fmt.Println(s[i])
	}

	//映射
	mm := map[string]int{"one": 111, "two": 2}
	fmt.Println(len(mm), mm["one"], mm["wtf"])

	fmt.Println(add(10, 2))

	n := 5
	add2(n)        //not working
	fmt.Println(n) // 5
	add2ptr(&n)    //working
	fmt.Println(n) // 7

	//初始化结构体
	cc := user{name: "wang", password: "1024"}
	fmt.Printf("%+v\n", cc) //{name:wang password:1024}
	fmt.Println(cc.name)
	fmt.Println(cc.password)

	//调用密码匹配结构体函数
	var dd user
	dd.resetPassword("2048")
	fmt.Println(dd.checkPassword("2048")) //true

	//调用错误函数
	u, err := findUser([]user{{"wang", "1024"}}, "wang")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(u.name)

	if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
		fmt.Println(err) //not found
		return
	} else {
		fmt.Println(u.name)
	}
}

🔑小项目实战

🐘猜数字游戏

描述

猜数字,猜大猜小了,都会有提示,知道最终猜中,结束循环

如果输入不合法,字符串不能转化为整型,就会提示Invalid Input

代码

Go 复制代码
// 数字猜测游戏
package main

import ( //导入第3方包
	"bufio"
	"fmt"
	_ "fmt" //format包, 输入输出字符串
	"math/rand"
	_ "math/rand"
	"os"
	"strconv"
	_ "strconv"
	"strings"
	"time"
	_ "time"
)

func main() {
	maxNum := 100
	//Goland 1.15后不需要随机数种子, 会自动调用math/rand包的函数
	rand.Seed(time.Now().UnixNano())
	secretNumber := rand.Intn(maxNum) //生成0~100随机数
	//fmt.Println("The secret number is ", secretNumber)

	fmt.Println("Please input your guess")
	reader := bufio.NewReader(os.Stdin) //os.Stdin得到Stdin文件, bufio.NewReader转成只读的流
	for {
		input, err := reader.ReadString('\n') //ReadString读入一行输入
		if err != nil {
			fmt.Println("An error occured while reading input. Please try again", err)
			continue
		}
		input = strings.TrimSuffix(input, "\r\n") //strings包的TrimSuffix函数去掉回车符和换行符

		guess, err := strconv.Atoi(input) //Atoi转数字
		if err != nil {
			fmt.Println("Invalid input. Please enter an integer value")
			continue
		}
		fmt.Println("Your 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
		}
	}
}

cmd测试

🐘在线词典

描述

先说下几个代码生成的工具(网址)👇

(1)👇Convert curl commands to Go (curlconverter.com)

用于将 cURL 命令转换为 Go 代码(也可以是其他语言)

cURL 是一个常用的命令行工具,用于发送 HTTP 请求并获取响应

将 cURL 命令转换为相应的编程语言代码,可以方便地在代码中,执行相同的 HTTP 请求

(2)👇JSON转Golang Struct - 在线工具 - OKTools

这个更好用,支持20多种转换

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

👆上面网址打开,开发者工具

右键name,Copy,Copy as Curl(bash)粘贴到下面网址👇 就转化成Go代码了(或者其他语言)

Convert curl commands to code

(1)用Go语言发送http请求,解析json

代码1 -- ++抓包++

输出一长串 json 字符

Go 复制代码
package main

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

func main() {
	client := &http.Client{}
	//字符串data转成流
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	//1.创建请求
	//创建http的POST请求, 第二个参数是url
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}

	//2.设置请求头
	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("device-id", "6febce1e00ffc1f9e33ddd0354386064")
	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="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
	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/115.0.0.0 Safari/537.36 Edg/115.0.1901.183")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	//3.发起请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err) //请求失败,退出进程
	}
	defer resp.Body.Close() //拿到请求后,通过defer关闭流
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText) //打印json字符串
}

👇由于最终要通过变量输入,而不是 json 字符串输入,所以需要 json 实例化

代码2 -- ++生成request body++

实例化一个 json,需要构造一个结构体,使结构体的名字和 json 的结构一一对应,再调用

json.Marshal()函数,举例👇

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
)

type userInfo struct {
	Name string
	Age int
	Hobby []string
}

func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a)
	//...
	buf, err = json.MarshalIndent(a, "", "\t")
	...
}

修改后的代码

Go 复制代码
package main

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

// 构造结构体
type DictRequest struct {
	TransType string `json:"trans_type"` //翻译
	Source    string `json:"source"`     //原文本
	UserID    string `json:"user_id"`    //用户ID
}

func main() {
	client := &http.Client{}
	//new一个结构体变量并初始化字段名
	request := DictRequest{TransType: "en2zh", Source: "good"}
	buf, err := json.Marshal(request) //json.Marshal实例化request
	//request转bytes数组
	var data = bytes.NewReader(buf)
	//1.创建请求
	//创建http的POST请求, 第二个参数是url
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}

	//2.设置请求头
	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("device-id", "6febce1e00ffc1f9e33ddd0354386064")
	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="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
	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/115.0.0.0 Safari/537.36 Edg/115.0.1901.183")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	//3.发起请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err) //请求失败,退出进程
	}
	defer resp.Body.Close() //拿到请求后,通过defer关闭流
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText) //打印json字符串
}

代码3 -- ++解析 response body++

将一大串 response 解析,获取里面的几个字段

按 request 的方式,构造结构体,结构体字段和返回的 response 一一对应,再将返回的 json 字符串反序列化到结构体里

但是,这里的字段非常复杂👆,这时需要一个 json 转Go Struct 的工具👇

JSON转Golang Struct - 在线工具 - OKTools

整块代码粘贴过去

右键copy value,转换-嵌套

Go 复制代码
type DictResponse 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      []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"`
}

代码4 -- ++打印结果++

Go 复制代码
var dictResponse DictResponse //定义DictResponse变量
	//json.Unmarshal将bodyText解析为结构体变量的值
	err = json.Unmarshal(bodyText, &dictResponse) //& 写入结构体
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v\n", dictResponse) //%#v 打印结构体 详细值

输出

但是我们只需要 音标(prons) 和 解释(explanations)

代码5 -- ++完善结果++

代码6 -- 完整代码

Go 复制代码
package main

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

// 构造结构体
type DictRequest struct {
	TransType string `json:"trans_type"` //翻译
	Source    string `json:"source"`     //原文本
	UserID    string `json:"user_id"`    //用户ID
}

// 结构体嵌套
type DictResponse 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      []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{}
	//new一个结构体变量并初始化字段名
	//request := DictRequest{TransType: "en2zh", Source: "good"} //固定字符串
	request := DictRequest{TransType: "en2zh", Source: word} //变量word
	buf, err := json.Marshal(request)                        //json.Marshal实例化request
	//request转bytes数组
	var data = bytes.NewReader(buf)
	//1.创建请求
	//创建http的POST请求, 第二个参数是url
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}

	//2.设置请求头
	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("device-id", "6febce1e00ffc1f9e33ddd0354386064")
	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="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
	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/115.0.0.0 Safari/537.36 Edg/115.0.1901.183")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	//3.发起请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err) //请求失败,退出进程
	}
	defer resp.Body.Close() //拿到请求后,通过defer关闭流
	bodyText, err := io.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 //定义DictResponse变量
	//json.Unmarshal将bodyText解析为结构体变量的值
	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) //range遍历数组每个元素并打印
	}
}

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

++cmd测试++

🐘Socks5代理服务器

🔑介绍

📚原理

SOCKS5代理服务器充当客户端和目标服务器之间的中介。它接收来自客户端的请求,并将其转发到目标服务器,然后将目标服务器的响应转发回客户端。这样,客户端可以通过代理服务器访问目标服务器上的资源,同时实现了客户端与目标服务器之间的隔离和隐藏👇

👆

  1. 客户端连接到SOCKS5代理服务器:客户端首先与SOCKS5代理服务器建立连接。这个连接是直接建立的,不会经过目标服务器

  2. 客户端发送代理请求:一旦与代理服务器建立连接,客户端就会向代理服务器发送代理请求。请求中包含要连接的目标服务器的地址、端口号和认证信息(如果需要)

  3. 代理服务器建立连接:代理服务器收到代理请求后,会解析其中的目标服务器地址和端口号,并尝试与目标服务器建立连接

  4. 代理服务器与目标服务器交换数据:一旦代理服务器成功地与目标服务器建立连接,它会在客户端和目标服务器之间进行数据中转。代理服务器接收来自客户端的数据,并将其发送给目标服务器;相应地,它从目标服务器接收数据,并将其发送回客户端

  5. 数据传输完成:当数据传输完成后,可以根据具体的需求来决定是否保持连接或断开连接

💻TCP echo server

Socks5代理服务器较为复杂,我们先从简化版本 TCP echo server开始

TCP回显服务器(TCP echo server)是一种基于TCP协议的网络服务器,它接收客户端发送的数据并将其原封不动地返回给客户端

不挣扎了,检索了2小时,都没跑通,最后群里一问,这玩意没法在Windows跑?!

++所以,下面👇只是粘贴代码上去,不作解释和运行测试++

Go 复制代码
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
		}
		_, err = conn.Write([]byte{b})
		if err != nil {
			break
		}
	}
}

🚢auth

...

Go 复制代码
package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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")
}

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

🏘请求阶段

...

Go 复制代码
package main

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

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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", cmd)
	}
	addr := ""
	switch atyp {
	case atypeIPV4:
		_, 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)

	// +----+-----+-------+------+----------+----------+
	// |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
}

🤪relay阶段

...

Go 复制代码
package main

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

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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", cmd)
	}
	addr := ""
	switch atyp {
	case atypeIPV4:
		_, 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
}

📚Hertz框架

资源

看一遍👇

一文学会 Go 的三个主流开发框架| 青训营笔记 - HikariLan's Blog (minecraft.kim)

李文周的👇

【置顶】Go语言学习之路/Go语言教程 | 李文周的博客 (liwenzhou.com)

Hertz中文教程👇

Hertz | CloudWeGo

Hertz(Github搭建安装学习全流程)👇

hertz/README_cn.md at develop · cloudwego/hertz (github.com)

相关推荐
《源码好优多》3 分钟前
基于Java Springboot未央商城管理系统
java·开发语言·spring boot
平头哥在等你3 分钟前
python特殊字符序列
开发语言·python·正则表达式
特种加菲猫10 分钟前
初阶数据结构之栈的实现
开发语言·数据结构·笔记
江-小北12 分钟前
Java基础面试题04:Iterator 和 ListIterator 的区别是什么?
java·开发语言
鸽鸽程序猿27 分钟前
【前端】javaScript
开发语言·前端·javascript
kylin王国33 分钟前
R语言p值矫正整的方法
开发语言·r语言·p值
捂月1 小时前
Spring Boot 核心逻辑与工作原理详解
java·spring boot·后端
凯子坚持 c1 小时前
C++之二叉搜索树:高效与美的极致平衡
开发语言·c++
菜鸟起航ing1 小时前
Java中日志采集框架-JUL、Slf4j、Log4j、Logstash
java·开发语言·log4j·logback
Nightselfhurt1 小时前
RPC学习
java·spring boot·后端·spring·rpc