GO语言工程实践课后作业:实现思路、代码以及路径记录(实践选题)|青训营
go build指令的语法
go build [build flags] [packages]
常用的build flags
-o
: 指定生成的可执行文件的输出路径和文件名。例如,go build -o myapp
将生成名为myapp
的可执行文件。-ldflags
: 设置链接时的标志。可以使用该标志传递自定义的链接标志,如版本信息、构建时间等。例如,go build -ldflags "-X main.version=1.0.0"
将在可执行文件中设置main.version
变量为1.0.0
。-a
: 强制重新编译所有依赖的包,而不仅仅是更新的包。-v
: 显示编译过程中的详细信息,包括编译的包和文件。
猜谜游戏
生成随机数
生成随机数需要用到math/rand软件包。
max := 100
rand.Seed(time.Now().UnixNano())
secretNum := rand.Intn(max)
rand.Intn的官方解释:Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n) from the default Source. It panics if n <= 0.
1. 介绍:
这是一个猜数字的小游戏。它生成一个随机的秘密数字,然后要求用户猜测这个数字,直到猜对为止。
2. 分析:
尽可能使代码的逻辑相对分散,可读性较好。
3. 思路:
- 将生成秘密数字的部分封装成一个函数,提高代码的模块性和可维护性。
- 将读取用户输入并转换为整数的逻辑封装成一个函数,以减少代码重复和提高可读性。
- 将错误处理封装成函数,以避免重复的错误处理代码。
4. 作业代码:
将代码进行了分解,将不同功能封装成了不同的函数,以及添加了错误处理函数。这样,每个函数都有一个清晰的目的,提高了代码的可读性和可维护性。
generateSecretNumber
函数用于生成随机秘密数字。readInput
函数用于读取用户输入并转换为整数。main
函数是游戏的主逻辑,它调用了上述两个函数,更清晰地表达了游戏的流程。
以下是作业代码:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
const maxNum = 100
func generateSecretNumber() int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(maxNum)
}
func readInput(reader *bufio.Reader) (int, error) {
input, err := reader.ReadString('\n')
if err != nil {
return 0, fmt.Errorf("an error occurred while reading input: %v", err)
}
input = strings.TrimSpace(input)
guess, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("invalid input: please enter an integer value")
}
return guess, nil
}
func main() {
secretNumber := generateSecretNumber()
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
for {
guess, err := readInput(reader)
if err != nil {
fmt.Println(err)
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, Good Job!!!")
break
}
}
}
一个issue
这个错误消息 "strconv.Atoi: parsing '12\r': invalid syntax" 表示在使用 strconv.Atoi()
将用户输入转换为整数时出现问题。
这个错误发生的原因是输入字符串中包含回车符 ('\r'
) 字符和换行符 ('\n'
) 字符。由于 strconv.Atoi()
期望一个不包含任何额外字符,如空格或换行符的有效整数字符串,所以这些特殊字符的存在导致了转换失败。
这个问题通常出现在从标准输入(键盘)读取输入时,在某些操作系统(如 Windows)上会出现。Windows 使用 '\r'
和 '\n'
字符来表示换行,而类Unix的系统(如 Linux、macOS)通常只使用 '\n'
。
为了解决这个问题,你可以修改删除尾部换行字符的代码,同时删除回车符(如果有的话)。一种方法是使用 strings.TrimSpace()
来替代 strings.TrimSuffix()
:
用这行代码替换:
input = strings.TrimSuffix(input, "\n")
使用这行代码:
input = strings.TrimSpace(input)
通过使用 strings.TrimSpace()
,回车符和任何开头或结尾的空白字符都将被移除,只保留用户输入的整数字符串。这样应该能够解决在使用 strconv.Atoi()
进行转换时出现的 "invalid syntax" 错误。
在线词典
- 需要通过F12的开发者工具来复制post命令的curl
遇到的问题
在这里复制到的curl没法在网站中解析:
原curl内容:
curl "https://api.interpreter.caiyunai.com/v1/dict" ^
-H "authority: api.interpreter.caiyunai.com" ^
-H "accept: application/json, text/plain, */*" ^
-H "accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" ^
-H "app-name: xy" ^
-H "content-type: application/json;charset=UTF-8" ^
-H "device-id: b8d6300e90ceab631564ace50d6c1c2d" ^
-H "origin: https://fanyi.caiyunapp.com" ^
-H "os-type: web" ^
-H "os-version;" ^
-H "referer: https://fanyi.caiyunapp.com/" ^
-H "sec-ch-ua: ^\^"Not/A)Brand^\^";v=^\^"99^\^", ^\^"Microsoft Edge^\^";v=^\^"115^\^", ^\^"Chromium^\^";v=^\^"115^\^"" ^
-H "sec-ch-ua-mobile: ?0" ^
-H "sec-ch-ua-platform: ^\^"Windows^\^"" ^
-H "sec-fetch-dest: empty" ^
-H "sec-fetch-mode: cors" ^
-H "sec-fetch-site: cross-site" ^
-H "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.188" ^
-H "x-authorization: token:qgemv4jr1y38jyq6vhvi" ^
--data-raw "^{^\^"trans_type^\^":^\^"en2zh^\^",^\^"source^\^":^\^"come^\^"^}" ^
--compressed
调整后并且可被解析的curl:
curl "https://api.interpreter.caiyunai.com/v1/dict" \
-H "authority: api.interpreter.caiyunai.com" \
-H "accept: application/json, text/plain, */*" \
-H "accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" \
-H "app-name: xy" \
-H "content-type: application/json;charset=UTF-8" \
-H "device-id: b8d6300e90ceab631564ace50d6c1c2d" \
-H "origin: https://fanyi.caiyunapp.com" \
-H "os-type: web" \
-H "os-version:" \
-H "referer: https://fanyi.caiyunapp.com/" \
-H "sec-ch-ua: \"^Not/A)Brand^\";v=\"99\", \"^Microsoft Edge^\";v=\"115\", \"^Chromium^\";v=\"115\"" \
-H "sec-ch-ua-mobile: ?0" \
-H "sec-ch-ua-platform: \"^Windows^\"" \
-H "sec-fetch-dest: empty" \
-H "sec-fetch-mode: cors" \
-H "sec-fetch-site: cross-site" \
-H "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.188" \
-H "x-authorization: token:qgemv4jr1y38jyq6vhvi" \
--data-raw "{\"trans_type\":\"en2zh\",\"source\":\"come\"}" \
--compressed
为什么要将curl请求转化为go代码
将 curl 请求转换为 Go 代码的一个常见原因是将命令行的操作转移到程序中,以便更灵活、可控地进行 HTTP 请求。以下是一些原因和优势:
- 集成性与模块化: 将 HTTP 请求代码集成到 Go 程序中可以使整个应用更加模块化和结构化。这样可以更方便地管理和组织代码。
- 类型安全: Go 是一门静态类型语言,这意味着在编译时可以捕获很多错误,如类型错误、函数参数错误等。这可以避免一些在运行时可能出现的错误。
- 错误处理: Go 的错误处理机制更加严格和清晰,有助于更好地处理异常情况,提供更好的用户体验和程序稳定性。
- 性能: Go 是一门编译型语言,通常比解释型语言更高效。通过将 HTTP 请求嵌入到 Go 代码中,可以提高执行效率。
- 跨平台支持: Go 可以轻松地构建跨平台的应用,您可以在不同操作系统上运行相同的代码。
- 可维护性: Go 代码通常比较干净、易读,遵循一致的编码风格。这有助于提高代码的可维护性。
- 代码版本控制: 将 HTTP 请求转换为 Go 代码后,可以方便地将这些代码与应用的其他部分一起纳入版本控制,从而更好地管理和跟踪代码的变化。
- 自动化测试: 将 HTTP 请求嵌入到 Go 测试中,可以更轻松地进行单元测试和自动化测试,从而提高代码质量。
总的来说,将 curl 请求转换为 Go 代码可以提供更多的控制和灵活性,同时也有助于提高代码质量、性能和可维护性。这不仅适用于简单的请求,还可以用于复杂的网络操作和集成。
生成的代码
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"come"}`)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
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", "b8d6300e90ceab631564ace50d6c1c2d")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
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.188")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
-
创建一个
http.Client
对象,用于发送 HTTP 请求:client := &http.Client{}
-
创建一个字符串作为请求的数据,使用
strings.NewReader
函数将 JSON 数据包装成io.Reader
接口:var data = strings.NewReader(`{"trans_type":"en2zh","source":"come"}`)
-
创建一个
http.Request
对象,表示要发送的请求。设置请求方法为 POST,URL 为目标 API 的地址,请求数据为之前创建的data
变量:req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) if err != nil { log.Fatal(err) }
-
设置请求头,包括各种自定义的头部信息:
req.Header.Set("authority", "api.interpreter.caiyunai.com") req.Header.Set("accept", "application/json, text/plain, */*") // ... 设置其他头部信息 ...
-
使用客户端执行请求,并获取响应对象:
resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
-
从响应的
Body
属性中读取返回的数据,使用io.ReadAll
函数将响应内容读取到bodyText
变量中:bodyText, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) }
-
打印响应内容到控制台:
fmt.Printf("%s\n", bodyText)
这段代码的目的是向 https://api.interpreter.caiyunai.com/v1/dict
发送一个 POST 请求,包含指定的请求头和 JSON 数据,然后获取响应并将其输出到控制台。
作业代码
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"log"
"net/http"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_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"`
Antonym []string `json:"antonym"`
Entry string `json:"entry"`
Explanations []string `json:"explanations"`
Source string `json:"source"`
Synonym []interface{} `json:"synonym"`
Type string `json:"type"`
WqxExample [][]string `json:"wqx_example"`
} `json:"dictionary"`
}
func query(word string) {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
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", "b8d6300e90ceab631564ace50d6c1c2d")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
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.188")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.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
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)
}
}
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)
}
- 定义结构体
DictRequest
和DictResponse
来映射请求和响应的 JSON 数据结构。 - 定义了一个
query
函数,用于发起词典查询请求。该函数接受一个单词作为参数。 - 在
query
函数内部,创建了一个 HTTP 客户端,并使用DictRequest
结构体构建查询请求的 JSON 数据。 - 使用
http.NewRequest
函数创建一个 POST 请求,设置 URL 和请求数据。然后设置请求头,包括各种自定义的头部信息。 - 执行请求,获取响应对象。如果发生错误,将会打印错误并退出。
- 关闭响应的 Body。
- 读取响应的内容,将其解析为字节数组。
- 检查响应的状态码,如果不是 200,则打印错误信息并退出。
- 使用
json.Unmarshal
函数将响应内容解析为DictResponse
结构体,以便后续访问和处理。 - 打印单词的英文发音和中文释义。
- 在
main
函数中,检查命令行参数的数量。如果不是 2 个参数(包括程序名和要查询的单词),则输出用法信息并退出。 - 获取要查询的单词,调用
query
函数执行查询操作。
这段代码实现了一个简单的词典查询工具,可以在命令行中输入要查询的英文单词,然后通过调用 API 获取该单词的中文释义和发音。
SOCKS5 代理服务器
这段代码实现了一个简单的 SOCKS5 代理服务器,用于接受客户端连接并进行代理转发。下面是代码的实现思路:
- 导入所需的包:
bufio
:用于缓冲输入输出。context
:用于控制并发操作的上下文管理。encoding/binary
:用于处理二进制数据。errors
:用于表示错误。fmt
:用于格式化输出。io
:用于输入输出操作。log
:用于日志记录。net
:用于网络通信。
- 定义一些常量:
socks5Ver
:SOCKS5 协议版本。cmdBind
:代表 CONNECT 请求的命令值。atypeIPV4
、atypeHOST
、atypeIPV6
:不同类型的地址。
- 当看代码时,了解每个函数的功能和作用非常重要。以下是代码中每个函数的详细介绍:
main
函数:- 这是程序的入口函数,用于启动 SOCKS5 代理服务器并监听客户端连接。
- 创建一个 TCP 服务器,监听在本地的 1080 端口上。
- 循环接受客户端连接,对每个客户端连接启动一个
process
协程进行处理。
process
函数:- 用于处理客户端连接,包括身份验证和连接请求处理。
- 参数
conn
是客户端连接的net.Conn
对象。 - 调用
auth
函数进行身份验证,如果验证失败则关闭连接。 - 调用
connect
函数处理连接请求,如果处理失败则关闭连接。
auth
函数:- 进行 SOCKS5 协议的身份验证阶段。
- 参数
reader
是从客户端连接中创建的bufio.Reader
对象。 - 参数
conn
是客户端连接的net.Conn
对象。 - 读取协议版本和支持的认证方法数量,验证协议版本为 SOCKS5,并返回支持的认证方法。
- 向客户端写入协议版本和选择的认证方法。
connect
函数:- 处理 CONNECT 请求阶段,建立与目标服务器的连接。
- 参数
reader
是从客户端连接中创建的bufio.Reader
对象。 - 参数
conn
是客户端连接的net.Conn
对象。 - 解析 CONNECT 请求头,获取目标服务器地址和端口。
- 根据地址和端口建立与目标服务器的连接。
- 向客户端写入 CONNECT 响应头,表示已建立连接。
func main()
中的协程:- 在数据拷贝协程中使用
context
控制操作的取消,以确保在连接关闭或出现错误时及时终止数据拷贝。 - 启动两个协程,一个从客户端到目标服务器进行数据拷贝,另一个从目标服务器到客户端进行数据拷贝。
- 使用
io.Copy
函数进行数据拷贝,将一个io.Reader
的数据拷贝到一个io.Writer
。
- 在数据拷贝协程中使用
- 在数据拷贝协程中使用
context
控制操作的取消,以确保在连接关闭或出现错误时及时终止数据拷贝。 - 代码结构简单明了,基于 SOCKS5 协议规范实现了基本的代理服务器功能。
实现代码
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
}