当前位置: 首页 > news >正文

Go语法入门 + 项目实战

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

👆

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

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

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

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

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

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

相关文章:

Go语法入门 + 项目实战

&#x1f442; Take me Hand Acoustic - Ccile Corbel - 单曲 - 网易云音乐 第3个小项目有问题&#xff0c;不能在Windows下跑&#xff0c;懒得去搜Linux上怎么跑了&#xff0c;已经落下进度了.... 目录 &#x1f633;前言 &#x1f349;Go两小时 &#x1f511;小项目实战 …...

QT控件通过qss设置子控件的对齐方式、大小自适应等

一些复杂控件&#xff0c;是有子控件的&#xff0c;每个子控件&#xff0c;都可以通过qss的双冒号选择器来选中&#xff0c;进行独特的样式定义。很多控件都有子控件&#xff0c;太多了&#xff0c;后面单独写一篇文章来介绍各个控件的子控件。这里就随便来几个例子 例如下拉列…...

基于java在线收银系统设计与实现

摘要 科技的力量总是在关键的地方改变着人们的生活&#xff0c;不仅如此&#xff0c;我们的生活也是离不开这样或者那样的科技改变&#xff0c;有的消费者没有时间去商场购物&#xff0c;那么电商和快递的结合让端口到消费者的距离不再遥远&#xff1b;有的房客因地域或者工作的…...

Linux--进程的新建状态

新建状态&#xff1a; 操作系统创建了进程的内核数据结构&#xff08;task_struct、mm_struct、页表&#xff09;&#xff0c;但是页表没有创建映射关系&#xff0c;而且磁盘里的程序的代码和数据未加载到物理内存...

区间dp,合并石子模板题

设有 N 堆石子排成一排&#xff0c;其编号为 1,2,3,…,N。 每堆石子有一定的质量&#xff0c;可以用一个整数来描述&#xff0c;现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆&#xff0c;合并的代价为这两堆石子的质量之和&#xff0c;合并后与这两堆石子相邻的…...

C++代码格式化工具clang-format详细介绍

文章目录 clang-format思考代码风格指南生成您的配置运行 clang-format禁用一段代码的格式设置clang-format的设置预览 clang-format 我曾在许多编程团队工作过&#xff0c;这些团队名义上都有“编程风格指南”。该指南经常被写下来并放置在开发人员很少查看的地方。几乎在每种…...

CentOS 7安装PostgreSQL 15版本数据库

目录 一、何为PostgreSQL&#xff1f; 二、PostgreSQL安装 2.1安装依赖 2.2 执行安装 2.3 数据库初始化 2.4 配置环境变量 2.5 创建数据库 2.6 配置远程 2.7 测试远程 三、常用命令 四、用户创建和数据库权限 一、何为PostgreSQL&#xff1f; PostgreSQL是以加州大学…...

QGraphicsView实现简易地图2『瓦片经纬度』

前文链接&#xff1a;QGraphicsView实现简易地图1『加载离线瓦片地图』 地图采用GCJ02 Web 墨卡托投影&#xff0c;最小坐标&#xff1a;(-180.00000000000000,-85.05112877980655)&#xff0c;最大坐标&#xff1a;(180.00000000000000,85.05112877980655)。瓦片地图单张图片像…...

医学图像重建—第一章笔记

序言 本书涵盖内容&#xff1a; 2D parallel beam imaging 2D fan beam imaging 3D parallel ray imaging 3D parallel plane imaging 3D cone beam imaging 算法包括&#xff1a;analytical method&#xff0c;iterative method 应用于&#xff1a; X-ray CT single photon…...

python-pytorch基础之神经网络分类

这里写目录标题 生成数据函数定义数据集定义loader加载数据定义神经网络模型测试输出是否为2个输入数据&#xff0c;输出结果 训练模型函数计算正确率 训练数据并保存模型测试模型准备数据加载模型预测对比结果 生成数据函数 import randomdef get_rectangle():widthrandom.ra…...

【C++ 程序设计】实战:C++ 变量实践练习题

目录 01. 变量&#xff1a;定义 02. 变量&#xff1a;初始化 03. 变量&#xff1a;参数传递 04. 变量&#xff1a;格式说明符 ① 占位符 “%d” 改为格式说明符 “%llu” ② 占位符 “%d” 改为格式说明符 “%f” 或 “%e” 05. 变量&#xff1a;字节数统计 06. 变量&a…...

微软对Visual Studio 17.7 Preview 4进行版本更新,新插件管理器亮相

近期微软发布了Visual Studio 17.7 Preview 4版本&#xff0c;而在这个版本当中&#xff0c;全新设计的扩展插件管理器将亮相&#xff0c;并且可以让用户可更简单地安装和管理扩展插件。 据了解&#xff0c;目前用户可以从 Visual Studio Marketplace 下载各式各样的 VS 扩展插…...

Kafka 入门到起飞 - Kafka怎么做到保障消息不会重复消费的? 消费者组是什么?

Kafka怎么做到避免消息重复消费的&#xff1f; 消费者组是什么&#xff1f; 消费者&#xff1a; 1、订阅Topic&#xff08;主题&#xff09; 2、从订阅的Topic消费&#xff08;pull&#xff09;消息&#xff0c; 3、将消费消息的offset&#xff08;偏移量&#xff09;保存在K…...

MongoDB 的增、查、改、删

Monogo使用 增 单条增加 db.member.insertOne({"name":"张三","age":18,"create":new Date()}) db.member.insert({"name":"李四1","age":18,"create":new Date()}) db.member.insertOne(…...

mysql常用操作命令

mysql常用操作命令 mysql:单进程多线程模型,一个SQL语句无法利用多个cpu core 一:基本命令 0.查看当前连接数 show global status like Thread$; show variables like "%timeout%"; show variables like "log_%";1.查看当前连接状态 show processlist…...

数学建模常见模型汇总

优化问题 线性规划、半定规划、几何规划、非线性规划、整数规划、多目标规划(分层序列法)、动态规划、存贮论、代理模型、响应面分析法、列生成算法 预测模型 微分方程、小波分析、回归分析、灰色预测、马尔可夫预测、时间序列分析(AR MAMA.RMA ARTMA LSTM神经网络)、混沌模…...

C#使用LINQ查询操作符实例代码(二)

目录 六、连表操作符 1、内连接2、左外连接(DefaultIfEmpty)3、组连接七、集合操作 八、分区操作符 1、Take()&#xff1a;2、TakeWhile()&#xff1a;3、Skip()&#xff1a;4、SkipWhile()&#xff1a;九、聚合操作符 1、Count&#xff1a; 返回集合项数。 2、LongCount&…...

jenkinsfile小试牛刀

序 本文主要演示一下如何用jenkinsfile来编译java服务 安装jenkins 这里使用docker来安装jenkins docker run --name jenkins-docker \ --volume $HOME/jenkins_home:/var/jenkins_home \ -p 8080:8080 jenkins/jenkins:2.416之后访问http://${yourip}:8080&#xff0c;然后…...

C++ xmake构建

文章目录 一、xmake.lua二、xmake常用语句 一、xmake.lua --xmake.luaset_project("XXX")add_rules("mode.debug", "mode.release") set_config("arch", "x64")if is_plat("windows") then -- the release modei…...

推荐带500创作模型的付费创作V2.1.0独立版系统源码

ChatGPT 付费创作系统 V2.1.0 提供最新的对应版本小程序端&#xff0c;上一版本增加了 PC 端绘画功能&#xff0c; 绘画功能采用其他绘画接口 – 意间 AI&#xff0c;本版新增了百度文心一言接口。 后台一些小细节的优化及一些小 BUG 的处理&#xff0c;前端进行了些小细节优…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...