👂 Take me Hand Acoustic - Cécile Corbel - 单曲 - 网易云音乐
第3个小项目有问题,不能在Windows下跑,懒得去搜Linux上怎么跑了,已经落下进度了....
目录
😳前言
🍉Go两小时
🔑小项目实战
🐘猜数字游戏
🐘在线词典
代码1 -- 抓包
代码2 -- 生成request body
代码3 -- 解析 response body
代码4 -- 打印结果
代码6 -- 完整代码
cmd测试
🐘Socks5代理服务器
🔑介绍
📚原理
💻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
代码 -- 复制可运行
package mainimport ("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 stringpassword 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 = 1var b, c int = 1, 3fmt.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 := 42switch v {case 100:fmt.Println(100)fallthroughcase 42:fmt.Println(42)fallthroughcase 1:fmt.Println(1)fallthroughdefault:fmt.Println("default")}for j := 7; j < 9; j++ {fmt.Println(j)}i := 1for i <= 3 {fmt.Println(i)i = i + 1}nums := []int{2, 3, 4}sum := 0for idx, num := range nums {fmt.Println("range to index", idx)sum += num}fmt.Println("sum:", sum)//遍历map, 得到 键 k 值 vm := make(map[string]int)m["hello"] = 0m["world"] = 1//if key and value both neededfor k, v := range m {fmt.Printf("%v %v\n", k, v)}//or only need keyfor k := range m {fmt.Printf("%v \n", k)}var aa [5]intfor i := 0; i < 3; i++ {aa[i] = ifmt.Println(aa[i])}bb := [5]int{1, 2, 3, 4, 5}fmt.Println(bb[4])var twoD [2][3]intfmt.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 := 5add2(n) //not workingfmt.Println(n) // 5add2ptr(&n) //workingfmt.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 userdd.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 foundreturn} else {fmt.Println(u.name)}
}
🔑小项目实战
🐘猜数字游戏
描述
猜数字,猜大猜小了,都会有提示,知道最终猜中,结束循环
如果输入不合法,字符串不能转化为整型,就会提示Invalid Input
代码
// 数字猜测游戏
package mainimport ( //导入第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 字符
package mainimport ("fmt""io""log""net/http""strings"
)func main() {client := &http.Client{}//字符串data转成流var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)//1.创建请求//创建http的POST请求, 第二个参数是urlreq, 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()函数,举例👇
package mainimport ("encoding/json""fmt"
)type userInfo struct {Name stringAge intHobby []string
}func main() {a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}buf, err := json.Marshal(a)//...buf, err = json.MarshalIndent(a, "", "\t")...
}
修改后的代码
package mainimport ("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请求, 第二个参数是urlreq, 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,转换-嵌套
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 -- 打印结果
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 -- 完整代码
package mainimport ("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} //变量wordbuf, err := json.Marshal(request) //json.Marshal实例化request//request转bytes数组var data = bytes.NewReader(buf)//1.创建请求//创建http的POST请求, 第二个参数是urlreq, 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代理服务器充当客户端和目标服务器之间的中介。它接收来自客户端的请求,并将其转发到目标服务器,然后将目标服务器的响应转发回客户端。这样,客户端可以通过代理服务器访问目标服务器上的资源,同时实现了客户端与目标服务器之间的隔离和隐藏👇
👆
客户端连接到SOCKS5代理服务器:客户端首先与SOCKS5代理服务器建立连接。这个连接是直接建立的,不会经过目标服务器
客户端发送代理请求:一旦与代理服务器建立连接,客户端就会向代理服务器发送代理请求。请求中包含要连接的目标服务器的地址、端口号和认证信息(如果需要)
代理服务器建立连接:代理服务器收到代理请求后,会解析其中的目标服务器地址和端口号,并尝试与目标服务器建立连接
代理服务器与目标服务器交换数据:一旦代理服务器成功地与目标服务器建立连接,它会在客户端和目标服务器之间进行数据中转。代理服务器接收来自客户端的数据,并将其发送给目标服务器;相应地,它从目标服务器接收数据,并将其发送回客户端
数据传输完成:当数据传输完成后,可以根据具体的需求来决定是否保持连接或断开连接
💻TCP echo server
Socks5代理服务器较为复杂,我们先从简化版本 TCP echo server开始
TCP回显服务器(TCP echo server)是一种基于TCP协议的网络服务器,它接收客户端发送的数据并将其原封不动地返回给客户端
不挣扎了,检索了2小时,都没跑通,最后群里一问,这玩意没法在Windows跑?!
所以,下面👇只是粘贴代码上去,不作解释和运行测试
package mainimport ("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
...
package mainimport ("bufio""fmt""io""log""net"
)const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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}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/PASSWORDver, 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
}
🏘请求阶段
...
package mainimport ("bufio""encoding/binary""errors""fmt""io""log""net"
)const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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/PASSWORDver, 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阶段
...
package mainimport ("bufio""context""encoding/binary""errors""fmt""io""log""net"
)const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 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/PASSWORDver, 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)