👂 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 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
代码
// 数字猜测游戏
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 字符
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()函数,举例👇
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")
...
}
修改后的代码
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,转换-嵌套
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 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代理服务器充当客户端和目标服务器之间的中介。它接收来自客户端的请求,并将其转发到目标服务器,然后将目标服务器的响应转发回客户端。这样,客户端可以通过代理服务器访问目标服务器上的资源,同时实现了客户端与目标服务器之间的隔离和隐藏👇
👆
客户端连接到SOCKS5代理服务器:客户端首先与SOCKS5代理服务器建立连接。这个连接是直接建立的,不会经过目标服务器
客户端发送代理请求:一旦与代理服务器建立连接,客户端就会向代理服务器发送代理请求。请求中包含要连接的目标服务器的地址、端口号和认证信息(如果需要)
代理服务器建立连接:代理服务器收到代理请求后,会解析其中的目标服务器地址和端口号,并尝试与目标服务器建立连接
代理服务器与目标服务器交换数据:一旦代理服务器成功地与目标服务器建立连接,它会在客户端和目标服务器之间进行数据中转。代理服务器接收来自客户端的数据,并将其发送给目标服务器;相应地,它从目标服务器接收数据,并将其发送回客户端
数据传输完成:当数据传输完成后,可以根据具体的需求来决定是否保持连接或断开连接
💻TCP echo server
Socks5代理服务器较为复杂,我们先从简化版本 TCP echo server开始
TCP回显服务器(TCP echo server)是一种基于TCP协议的网络服务器,它接收客户端发送的数据并将其原封不动地返回给客户端
不挣扎了,检索了2小时,都没跑通,最后群里一问,这玩意没法在Windows跑?!
所以,下面👇只是粘贴代码上去,不作解释和运行测试
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
...
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
}
🏘请求阶段
...
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阶段
...
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)