四平网站优化,做直播app的公司,123上网之家网址,搜狗竞价#x1f442; Take me Hand Acoustic - Ccile Corbel - 单曲 - 网易云音乐 第3个小项目有问题#xff0c;不能在Windows下跑#xff0c;懒得去搜Linux上怎么跑了#xff0c;已经落下进度了.... 目录
#x1f633;前言
#x1f349;Go两小时
#x1f511;小项目实战
… 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站新手视频处查阅需要一遍又一遍bingGoogle百度GPTstackOverFlowyoutubeB站一共7个工具记好了加上算法群项目群在职群....效率不会低的 还需要很多文档...
Go两小时
注意先安装GolandGit弄个Github学生认证免费使用
跟着敲一遍182行代码先有个印象
从 Java 的角度初识 Go 语言 青训营笔记 - HikariLans Blog (minecraft.kim) 跟着敲完180行入门代码还不过瘾的话再跟这个5小时教程巩固一下基础 【golang教学】第二章golang的基础知识——结构包变量初探1010工作室出品_哔哩哔哩_bilibili 代码 -- 复制可运行
package mainimport (errorsfmttime
)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] as[1] bs[2] cfor 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方包bufiofmt_ fmt //format包, 输入输出字符串math/rand_ math/randosstrconv_ strconvstringstime_ 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测试 在线词典
描述
先说下几个代码生成的工具网址 1Convert curl commands to Go (curlconverter.com) 用于将 cURL 命令转换为 Go 代码也可以是其他语言 cURL 是一个常用的命令行工具用于发送 HTTP 请求并获取响应 将 cURL 命令转换为相应的编程语言代码可以方便地在代码中执行相同的 HTTP 请求 2JSON转Golang Struct - 在线工具 - OKTools 这个更好用支持20多种转换 彩云小译 - 在线翻译 (caiyunapp.com)
上面网址打开开发者工具 右键nameCopyCopy as Curlbash粘贴到下面网址 就转化成Go代码了(或者其他语言)
Convert curl commands to code 1用Go语言发送http请求解析json 代码1 -- 抓包
输出一长串 json 字符
package mainimport (fmtiolognet/httpstrings
)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;q0.9,en;q0.8,en-GB;q0.7,en-US;q0.6)req.Header.Set(app-name, xy)req.Header.Set(content-type, application/json;charsetUTF-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;v99, Microsoft Edge;v115, Chromium;v115)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/jsonfmt
)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 (bytesencoding/jsonfmtiolognet/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;q0.9,en;q0.8,en-GB;q0.7,en-US;q0.6)req.Header.Set(app-name, xy)req.Header.Set(content-type, application/json;charsetUTF-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;v99, Microsoft Edge;v115, Chromium;v115)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:rcWiki struct {} json:wikiDictionary struct {Prons struct {EnUs string json:en-usEn string json:en} json:pronsExplanations []string json:explanationsSynonym []string json:synonymAntonym []string json:antonymWqxExample [][]string json:wqx_exampleEntry string json:entryType string json:typeRelated []interface{} json:relatedSource 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 (bytesencoding/jsonfmtiolognet/httpos
)// 构造结构体
type DictRequest struct {TransType string json:trans_type //翻译Source string json:source //原文本UserID string json:user_id //用户ID
}// 结构体嵌套
type DictResponse struct {Rc int json:rcWiki struct {} json:wikiDictionary struct {Prons struct {EnUs string json:en-usEn string json:en} json:pronsExplanations []string json:explanationsSynonym []string json:synonymAntonym []string json:antonymWqxExample [][]string json:wqx_exampleEntry string json:entryType string json:typeRelated []interface{} json:relatedSource 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;q0.9,en;q0.8,en-GB;q0.7,en-US;q0.6)req.Header.Set(app-name, xy)req.Header.Set(content-type, application/json;charsetUTF-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;v99, Microsoft Edge;v115, Chromium;v115)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 (bufiolognet
)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 (bufiofmtiolognet
)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: 对应NMETHODSNMETHODS的值为多少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 (bufioencoding/binaryerrorsfmtiolognet
)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: 对应NMETHODSNMETHODS的值为多少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 | X00 | 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 | X00 | 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 (bufiocontextencoding/binaryerrorsfmtiolognet
)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: 对应NMETHODSNMETHODS的值为多少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 | X00 | 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 | X00 | 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 的三个主流开发框架 青训营笔记 - HikariLans Blog (minecraft.kim)
李文周的
【置顶】Go语言学习之路/Go语言教程 | 李文周的博客 (liwenzhou.com)
Hertz中文教程
Hertz | CloudWeGo
HertzGithub搭建安装学习全流程
hertz/README_cn.md at develop · cloudwego/hertz (github.com)