猜谜游戏、在线词典、SOCKS5服务器的实现,优化与代码解读 | 青训营笔记

猜谜游戏

代码主要修改有两点:

  1. rand.Seed()方法在go1.20已经弃用,我采用rand.New(rand.NewSource())来实现。
  2. 读取输入的方式由bufio.NewReader(os.Stdin)改为fmt.Scanf("%d", &guess)

以下是源代码:

go 复制代码
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	maxNum := 100
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	secretNumber := r.Intn(maxNum)

	fmt.Println("Please input your guess:")
	var guess int
	for {
		_, err := fmt.Scanf("%d", &guess)
		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
		}
	}
}

解释: 这段Go代码是一个简单的猜数字游戏。程序开始时会生成一个随机的秘密数字,并提示玩家输入猜测的数字。玩家每次输入一个数字后,程序会告诉玩家这个数字是偏大还是偏小,直到玩家猜对为止。如果输入非数字或者在读取输入时出现错误,程序会提示玩家重新输入。玩家猜对秘密数字后,游戏结束并输出"Correct, you Legend!"。

这个猜数字游戏的实现逻辑如下:

  1. 确定游戏的最大可猜测数字maxNum,在代码中设定为100。
  2. 使用rand.NewSource(time.Now().UnixNano())创建一个随机数生成器,并以当前时间的纳秒数作为种子,确保每次运行程序时生成的随机数是不同的。
  3. 使用随机数生成器的Intn(maxNum)方法生成一个0到maxNum-1之间的随机整数作为秘密数字secretNumber
  4. 提示玩家输入猜测的数字,并使用fmt.Scanf("%d", &guess)读取玩家的输入。
  5. 进入游戏循环,循环中玩家可以反复输入猜测的数字直到猜对为止。
  6. 在每次循环中,首先读取玩家输入的内容并处理可能出现的错误。
  7. 使用strconv.Atoi(input)将输入的字符串转换为整数类型的猜测数字guess。若输入的内容非数字,则提示玩家重新输入。
  8. 检查猜测数字guess与秘密数字secretNumber的大小关系,并给出相应的提示信息。
  9. 如果猜测数字guess大于秘密数字secretNumber,则提示玩家继续尝试,如果猜测数字小于秘密数字,则同样提示玩家继续尝试。
  10. 如果玩家猜对了秘密数字,输出"Correct, you Legend!",并结束游戏循环。
  11. 玩家猜对后,游戏结束,程序运行结束。

在线词典

火山翻译

首先用火山翻译实现英译汉。

go 复制代码
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"unicode"
)

type DictRequestHS struct {
	Source         string   `json:"source"`
	Words          []string `json:"words"`
	SourceLanguage string   `json:"source_language"`
	TargetLanguage string   `json:"target_language"`
}
type DictResponseHS struct {
	Details []struct {
		Detail string `json:"detail"`
		Extra  string `json:"extra"`
	} `json:"details"`
}

type DictResponseHSData struct {
	Result []struct {
		Ec struct {
			Basic struct {
				Explains []struct {
					Pos   string `json:"pos"`
					Trans string `json:"trans"`
				} `json:"explains"`
			} `json:"basic"`
		} `json:"ec"`
	} `json:"result"`
}

func query() {
	for {
		fmt.Println("请输入要查询的内容:")
		reader := bufio.NewReader(os.Stdin)
		word, _ := reader.ReadString('\n')
		word = strings.Trim(word, "\r\n")
		if IsEnglishString(word) {
			queryHS(word)
			break
		} else {
			fmt.Println("请输入英语")
		}
	}

}
func queryHS(word string) {

	client := &http.Client{}
	request := DictRequestHS{"youdao", []string{word}, "en", "zh"}
	buf, err := json.Marshal(request)
	if err != nil {
		log.Fatal(err)
	}
	var data = bytes.NewReader(buf)
	req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/dict/detail/v1/?msToken=&X-Bogus=DFSzswVOQDaibrQ3tJHN7cppgiFh&_signature=_02B4Z6wo00001g0lO6gAAIDD-FrRNX0w-.4NJT8AAOfuf7", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("authority", "translate.volcengine.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("content-type", "application/json")
	req.Header.Set("cookie", "x-jupiter-uuid=16888064002651706; i18next=zh-CN; s_v_web_id=verify_ljtrq6kx_UW3ieIzP_8gQX_4abc_B8D8_AoHwuLysn026; ttcid=db98bce9149b4f09b905a71503d9331e36")
	req.Header.Set("origin", "https://translate.volcengine.com")
	req.Header.Set("referer", "https://translate.volcengine.com/?category=&home_language=zh&source_language=detect&target_language=zh&text=bad")
	req.Header.Set("sec-ch-ua", `"Not.A/Brand";v="8", "Chromium";v="114", "Microsoft Edge";v="114"`)
	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", "same-origin")
	req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.43")
	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)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
	}

	var dictResponse DictResponseHS

	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("火山翻译")
	item := dictResponse.Details[0]
	jsonStr := item.Detail

	var HSData DictResponseHSData
	err = json.Unmarshal([]byte(jsonStr), &HSData)
	if err != nil {
		panic(err)
	}

	for _, item := range HSData.Result[0].Ec.Basic.Explains {
		fmt.Println(item.Pos, item.Trans)
	}
}

func IsEnglishString(s string) bool {
	for _, r := range s {
		if unicode.IsLetter(r) && !unicode.Is(unicode.Scripts["Han"], r) {
			continue
		} else {
			return false
		}
	}
	return true
}

func main() {
	defer func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg, "输入不合法,请重新输入")
		}
	}()
	query()
}

解释:

  1. 结构体定义:
  • DictRequestHS: 用于构建向火山翻译API发送请求的数据结构,包含要翻译的source、单词数组words、源语言source_language和目标语言target_language
  • DictResponseHS: 火山翻译API的响应数据结构,包含details字段,其中的detail存储了包含翻译和词性解释信息的JSON字符串。
  • DictResponseHSData: 用于解析翻译结果中的JSON字符串,提取出具体的词性和翻译信息。
  1. query() 函数:
  • 在此函数中,程序提示用户输入要查询的内容,并使用 IsEnglishString() 函数检查输入是否为合法的英文单词。
  • 如果输入合法,将调用 queryHS() 函数进行英文单词的查询。
  1. queryHS(word string) 函数:
  • 在此函数中,构建 HTTP 请求,使用火山翻译API查询给定单词的中文翻译和词性解释。
  • 程序构建了一个 http.Client 客户端,发送 POST 请求到火山翻译API。
  • 将查询信息封装为 DictRequestHS 结构体,然后使用 json.Marshal() 将其转换为 JSON 格式的数据,并发送到 API。
  • API响应的结果以 JSON 格式返回,并经过解析,提取出翻译和词性解释信息。
  1. IsEnglishString(s string) bool 函数:
  • 此函数用于判断输入的字符串是否为合法的英文单词。
  • 通过遍历字符串中的每个字符,使用 unicode.IsLetter(r) 判断是否为字母,并通过 !unicode.Is(unicode.Scripts["Han"], r) 排除中文字符。
  • 如果字符串中包含非英文字符或汉字,则返回 false,否则返回 true
  1. main() 函数:
  • 程序入口函数,调用 query() 函数开始查询流程。
  • 使用 defer 延迟执行一个匿名函数,用于捕捉可能出现的异常。如果在查询过程中出现错误,会输出错误信息并提示用户重新输入。

运行示例:

彩云翻译和百度翻译

运用goroutine,结合sync包,同时实现彩云翻译和百度翻译。可对输入的内容自动检测,完成英译汉,汉译英。

go 复制代码
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
)

var wg sync.WaitGroup

type DictRequestCY struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}

type DictResponseCY struct {
	Rc   int `json:"rc"`
	Wiki struct {
		KnownInLaguages int `json:"known_in_laguages"`
		Description     struct {
			Source string      `json:"source"`
			Target interface{} `json:"target"`
		} `json:"description"`
		ID   string `json:"id"`
		Item struct {
			Source string `json:"source"`
			Target string `json:"target"`
		} `json:"item"`
		ImageURL  string `json:"image_url"`
		IsSubject string `json:"is_subject"`
		Sitelink  string `json:"sitelink"`
	} `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"`
}

type DictResponseBD struct {
	Errno int `json:"errno"`
	Data  []struct {
		K string `json:"k"`
		V string `json:"v"`
	} `json:"data"`
}

func queryBD(word string) {
	client := &http.Client{}
	var data = strings.NewReader("kw=" + word)
	req, err := http.NewRequest("POST", "https://fanyi.baidu.com/sug", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
	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("Connection", "keep-alive")
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Set("Cookie", "BIDUPSID=1CDF4C78BCFB3D90B2D6A594920DA6E4; PSTM=1630726121; __yjs_duid=1_02f9d28d226309370f287ee032114e3f1630763189674; REALTIME_TRANS_SWITCH=1; HISTORY_SWITCH=1; FANYI_WORD_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; BAIDUID=396D4FB45E415311D3C31A0DE0D1AF80:FG=1; BDUSS=V6NWkyMzJnaTB-OWplSFdUR25tRkxsTzFnVnBDanN4ZEs2bHFMc2EzaXJOcDVrRVFBQUFBJCQAAAAAAAAAAAEAAACpaydUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKupdmSrqXZkMV; BDUSS_BFESS=V6NWkyMzJnaTB-OWplSFdUR25tRkxsTzFnVnBDanN4ZEs2bHFMc2EzaXJOcDVrRVFBQUFBJCQAAAAAAAAAAAEAAACpaydUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKupdmSrqXZkMV; APPGUIDE_10_6_2=1; MCITY=-75%3A; MAWEBCUID=web_jcjIPqwGUVRAlyJeYpGiILrRIWXcMWNDAjChgRXUZoHLpLyApr; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; delPer=0; PSINO=1; BAIDUID_BFESS=396D4FB45E415311D3C31A0DE0D1AF80:FG=1; BA_HECTOR=2h852l8h2ha5840k8k0k050g1iai2fd1o; ZFY=kYWZ1Y6d6SV8cMcgDFNKAhwEEhlIK7W5gu1C:APN07HM:C; H_PS_PSSID=36546_39026_39022_38858_38957_38954_39009_38918_38972_38815_38637_26350_39041_38948_39046; ab_sr=1.0.1_MjBiYTAxMmUwMWE2MzIyODYxY2JiYzIxNjFjMWY2OTIwOTc5NTA1MTI5MzAzZGI2OWNiMGEwM2I1Y2IwNjJiZWEzNDQwOTg1ZTkyYTJkNmU1OTc3MjY1MTJhZTM1MGEwNWNlM2NkM2VjNWM0NDMwZjY0ZWJlZTEyOTQyZjFkZjE1YzhiYzY4YjBhYzQ4NzE2NWI4MjNkZjA1NTVkMDk2MmRjMjZhYTA4NThmOTYwMmEzOWMxMjAxMGM2OTdjODhm")
	req.Header.Set("Origin", "https://fanyi.baidu.com")
	req.Header.Set("Referer", "https://fanyi.baidu.com/")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Site", "same-origin")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.43")
	req.Header.Set("X-Requested-With", "XMLHttpRequest")
	req.Header.Set("sec-ch-ua", `"Not.A/Brand";v="8", "Chromium";v="114", "Microsoft Edge";v="114"`)
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("sec-ch-ua-platform", `"Windows"`)
	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)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
	}
	var dictResponse DictResponseBD
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("百度翻译")
	for _, item := range dictResponse.Data {
		fmt.Println(item.V)
	}

	wg.Done()

}
func queryCY(word string) {
	client := &http.Client{}
	request := DictRequestCY{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("Connection", "keep-alive")
	req.Header.Set("DNT", "1")
	req.Header.Set("os-version", "")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
	req.Header.Set("app-name", "xy")
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("device-id", "")
	req.Header.Set("os-type", "web")
	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("Sec-Fetch-Site", "cross-site")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
	req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
	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)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
	}
	var dictResponse DictResponseCY
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("彩云翻译")
	fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
	for _, item := range dictResponse.Dictionary.Explanations {
		fmt.Println(item)
	}

	wg.Done()
}

func main() {

	fmt.Println("请输入要查询的内容:")
	reader := bufio.NewReader(os.Stdin)
	word, _ := reader.ReadString('\n')
	word = strings.Trim(word, "\r\n")

	wg.Add(2)

	go queryCY(word)

	go queryBD(word)

	wg.Wait()
}

解释:

代码中包含了两个查询函数,分别使用百度翻译API和彩云翻译API来获取结果。以下是代码的结构逻辑和实现:

  1. 结构体定义:
  • DictRequestCY: 用于构建向彩云翻译API发送请求的数据结构,包含TransType表示翻译类型,Source表示要翻译的英文单词。
  • DictResponseCY: 彩云翻译API的响应数据结构,包含Dictionary字段,其中包含了中文翻译和词性解释的信息。
  • DictResponseBD: 百度翻译API的响应数据结构,包含Data字段,其中包含了中文翻译的信息。
  1. queryBD(word string) 函数:
  • 在此函数中,构建 HTTP 请求,使用百度翻译API查询给定单词的中文翻译。
  • 发送 POST 请求到百度翻译API,将要查询的单词作为参数。
  • 解析 API 响应,提取出翻译信息,并输出到命令行。
  1. queryCY(word string) 函数:
  • 在此函数中,构建 HTTP 请求,使用彩云翻译API查询给定单词的中文翻译和词性解释。
  • 发送 POST 请求到彩云翻译API,将要查询的单词作为参数。
  • 解析 API 响应,提取出中文翻译和词性解释信息,并输出到命令行。
  1. main() 函数:
  • 程序入口函数,提示用户输入要查询的英文单词。
  • 使用 sync.WaitGroup 来启动并发的查询函数,分别调用 queryCY(word)queryBD(word) 查询彩云翻译和百度翻译的结果。
  • Wait() 保证两个查询函数都完成后,才会继续执行后续的代码。

这段代码通过构建合适的数据结构和HTTP请求,与百度翻译API和彩云翻译API进行交互,实现了查询英文单词的中文翻译和词性解释的功能。用户可以在命令行中输入英文单词,程序会输出彩云翻译和百度翻译的结果。由于使用了并发,两个查询函数可以同时进行,提高了查询效率。

运行示例:

工具推荐

1.json格式数据转golang结构体

JSON转Golang Struct - 在线工具 - OKTools

2.curl转爬虫代码

Convert curl commands to code (curlconverter.com)

SOCKS5服务器

SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。

我们先来看一下最终写完的代理服务器的效果。我们启动这个程序,然后在浏览器里面配置使用这个代理,此时我们打开网页。 代理服务器的日志,会打印出你访问的网站的域名或者IP,这说明我们的网络流量是通过这个代理服务器的。我们也能在命令行去测试我们的代理服务器。我们可以用curl --socks5 +代理服务器地址,后面加一个可访问的URL,如果代理服务器工作正常的话,那么 curl命令就会正常返回。

socks5协议原理:

源代码:

go 复制代码
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

	// Read VER, NMETHODS, and METHODS
	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    |
	// +----+--------+

	// Write VER and METHOD
	_, 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个字节

	// Read VER, CMD, RSV, and ATYP
	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
	
	// Send response to client
	_, 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
}

解释:主函数开始监听在本地地址(127.0.0.1)的1080端口上,接受客户端的连接请求,并在新的goroutine中处理客户端连接。process函数用于处理客户端连接。首先关闭连接,然后创建一个bufio.Reader用于读取数据。接下来调用auth函数进行认证,然后调用connect函数进行连接。auth函数用于处理认证过程。根据SOCKS5协议规定的格式,从reader中读取协议版本和支持的认证方法,然后向客户端返回协议版本和选择的认证方法。这里只实现了不需要认证的方式。connect函数用于处理连接请求。根据SOCKS5协议规定的格式,从reader中读取版本、命令、地址类型和目标地址信息。根据地址类型的不同,解析出目标地址和端口号。然后建立与目标服务器的连接,并将连接成功的响应发送给客户端。接下来,使用goroutine并发地将客户端的数据复制到目标服务器和将目标服务器的数据复制回客户端,从而实现了数据的转发。最后,使用<-ctx.Done()阻塞等待上述goroutine的完成。

将这段代码在goland上运行,并在cmd命令行(管理员)中输入curl --socks5 127.0.0.1 1080 -v http://www.baidu.com以测试:

也可以在浏览器上测试我们的SOCKS5服务器。打开谷歌浏览器,下载插件:SwitchyOmega

按如下操作配置,配置完成后点击应用选项以保存更改。

点击该插件,切换到之前设置的proxy。我们打开稀土掘金页面来测试:

以下是goland的运行结果,我们发现结果多了很多行。

至此,用go实现SOCKS5服务器成功。

相关推荐
Find18 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子18 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子18 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子18 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵20 天前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六21 天前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz23 天前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity56524 天前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记