一.猜题游戏生成随机数

1.0 生成随机数

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	maxNum := 100
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is", secretNumber)
}

发现每次都会打印相同数字屏幕上(81)

开发环境可能可以打印不同数字(但不推荐1.0)

我们可以查看 这个包的文档。文档的开头例子告诉我们使用它之前需要设置随机数种子,否则的话每一次都会生成相同随机数序列。一般惯例用法是在程序启动时候,用启动时间戳来初始化随机种子,我们用 time.Now().UnixNano() 来初始化随机种子

1.1

package main

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

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano())  //  设置随机数种子
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)
}

结果会是在 0 到 100 之间

2.0 读取用户输入

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano())  //  设置随机数种子
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)

	fmt.Println("Please input your guess")
	reader := bufio.NewReader(os.Stdin)  //把一个文件转换成一个 reader 变量
	input, err := reader.ReadString('n')  //读取一行
	if err != nil {
		fmt.Println("An error occured while reading input. Please try again", err)
		return
	}
	input = strings.TrimSuffix(input, "n") //  去掉换行符,注意Macn,windows自动加入的是rn

	guess, err := strconv.Atoi(input)  //  将字符串转换成数字
	if err != nil {
		fmt.Println("Invalid input. Please enter an integer value")
		return
	}
	fmt.Println("You guess is", guess)
}

每个程序执行的时候都会打开几个文件stdin stdout stderr等,stdin 文件可以os.Stdin 来得到。

然后直接操作这个文件很不方便,我们会用 bufio.NewReader 把一个文件转换成一个 reader 变量

reader 变量上会有很多用来操作一个流的操作,我们可以用它的 ReadString 方法来读取一行

如果失败了的话,我们会打印错误并能退出。ReadString 返回结果包含结尾的回车符换行符去掉,再用strconv.Atoi()转换成数字。如果转换失败,我们同样打印错误退出

3.0 实现判断逻辑

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!")
	}

4.0 完整游戏

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano()) //  设置随机数种子
	secretNumber := rand.Intn(maxNum)
	// fmt.Println("The secret number is ", secretNumber)

	fmt.Println("Please input your guess")
	reader := bufio.NewReader(os.Stdin) //把一个文件转换一个 reader 变量
	for {
		input, err := reader.ReadString('n') //读取一行
		if err != nil {
			fmt.Println("An error occured while reading input. Please try again", err)
			continue
		}
		input = strings.TrimSuffix(input, "n") //  去掉换行符

		guess, err := strconv.Atoi(input) //  将字符串转换成数字
		if err != nil {
			fmt.Println("Invalid input. Please enter an integer value")
			continue
		}
		fmt.Println("You 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
		}
	}
}

 二.在线词典

1.准备(以Google浏览器,Mac系统操作

首先打开网址彩云小译-网页翻译-文献翻译-在线翻译

查询一个单词通过调用第三方的 API 查询单词翻译打印出来。

在这个项目里面学习如何go 语言发送 HTTP 请求解析 json学习如何使用代码生成提高开发效率

2.抓包

用到的 API 是彩云在线翻译,以此为例

点击翻译浏览器会发送系列请求找到查询单词请求。这是一个 HTTP 的 post请求(相当复杂),请求header 是一个 json里面两个字段代表翻译语言查询单词source就是查询的词。API 的返回结果里面会有 Wikidictionary 两个字段。需要用的结果主要在 dictionary.Explanations 字段里面,其他字段里面还包括音标等信息

 

3.代码生成

curl 'https://api.interpreter.caiyunai.com/v1/translator' 
  -X 'OPTIONS' 
  -H 'authority: api.interpreter.caiyunai.com' 
  -H 'accept: */*' 
  -H 'accept-language: zh-CN,zh;q=0.9' 
  -H 'access-control-request-headers: app-name,content-type,device-id,os-type,os-version,t-authorization,version,x-authorization' 
  -H 'access-control-request-method: POST' 
  -H 'origin: https://fanyi.caiyunapp.com' 
  -H 'referer: https://fanyi.caiyunapp.com/' 
  -H 'sec-fetch-dest: empty' 
  -H 'sec-fetch-mode: cors' 
  -H 'sec-fetch-site: cross-site' 
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 
  --compressed ;
curl 'https://api.interpreter.caiyunai.com/v1/translator' 
  -H 'authority: api.interpreter.caiyunai.com' 
  -H 'accept: application/json, text/plain, */*' 
  -H 'accept-language: zh-CN,zh;q=0.9' 
  -H 'app-name: xy' 
  -H 'content-type: application/json;charset=UTF-8' 
  -H 'device-id: 5107a8a7278db425e7bce568842b1261' 
  -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="8", "Chromium";v="114", "Google Chrome";v="114"' 
  -H 'sec-ch-ua-mobile: ?0' 
  -H 'sec-ch-ua-platform: "macOS"' 
  -H 'sec-fetch-dest: empty' 
  -H 'sec-fetch-mode: cors' 
  -H 'sec-fetch-site: cross-site' 
  -H 't-authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJicm93c2VyX2lkIjoiNTEwN2E4YTcyNzhkYjQyNWU3YmNlNTY4ODQyYjEyNjEiLCJpcF9hZGRyZXNzIjoiMjIzLjcyLjU1LjMxIiwidG9rZW4iOiJxZ2VtdjRqcjF5MzhqeXE2dmh2aSIsInZlcnNpb24iOjEsImV4cCI6MTY5MDM2NjQ1Nn0.CJyHVaSyAKSfVjtRlXx4trDK57TvhLAewH-rkhQWrag' 
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 
  -H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' 
  --data-raw '{"source":"good","trans_type":"auto2zh","request_id":"web_fanyi","media":"text","os_type":"web","dict":true,"cached":true,"replaced":true,"style":"formal","detect":true,"browser_id":"5107a8a7278db425e7bce568842b1261"}' 
  --compressed ;
curl 'https://api.interpreter.caiyunai.com/v1/dict' 
  -X 'OPTIONS' 
  -H 'authority: api.interpreter.caiyunai.com' 
  -H 'accept: */*' 
  -H 'accept-language: zh-CN,zh;q=0.9' 
  -H 'access-control-request-headers: app-name,content-type,device-id,os-type,os-version,version,x-authorization' 
  -H 'access-control-request-method: POST' 
  -H 'origin: https://fanyi.caiyunapp.com' 
  -H 'referer: https://fanyi.caiyunapp.com/' 
  -H 'sec-fetch-dest: empty' 
  -H 'sec-fetch-mode: cors' 
  -H 'sec-fetch-site: cross-site' 
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 
  --compressed ;
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' 
  -H 'app-name: xy' 
  -H 'content-type: application/json;charset=UTF-8' 
  -H 'device-id: 5107a8a7278db425e7bce568842b1261' 
  -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="8", "Chromium";v="114", "Google Chrome";v="114"' 
  -H 'sec-ch-ua-mobile: ?0' 
  -H 'sec-ch-ua-platform: "macOS"' 
  -H 'sec-fetch-dest: empty' 
  -H 'sec-fetch-mode: cors' 
  -H 'sec-fetch-site: cross-site' 
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 
  -H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' 
  --data-raw '{"trans_type":"en2zh","source":"good"}' 
  --compressed

打开代码生成网址

 Convert curl commands to Go

复制粘贴过来,自动生成 Golang 代码

需要在 Golang 里面发送这个请求。但是这个请求比较复杂,用代码构造很麻烦。可以用一种简单方式来生成代码右键浏览器里面copy as curl,在终端粘贴一下 curl 命令可以返回一大串 json

4.生成代码解读

5.生成request body/解析request body

在 Golang 里面,需要生成一段 JSON ,常用方式是先构造出来一个结构体,这个结构体和需要生成的 JSON 的结构是一一对应的。

通过网站转换结构体

JSON转Golang Struct – 在线工具 – OKTools

将Preview复制过来转换成结构体

 完整代码展示

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 := &amp;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, &amp;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 := &amp;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, &amp;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, "rn")

	wg.Add(2)

	go queryCY(word)

	go queryBD(word)

	wg.Wait()
}
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, "rn")
		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()
}

火山翻译

三.SOCKS5 代理介绍

socks5是网络传输协议的一种,位于表示层传输层之间,主要用于客户端与外网服务器之间通讯的中间传递。它是一种通用代理服务器,可以允许有权限用户穿过防火墙限制,使得高权限用户可以访问外部资源。大量网络应用程序支持socks5代理

原理

Socks5是一种网络传输协议,位于OSI模型中的表示层传输层之间,主要用于客户端与外网服务器之间通讯的中间传递。Socks5协议工作原理包括以下步骤
客户端向代理服务器发出请求信息用于协商版本和认证方法
代理服务器应答,将选择方法发送给客户端。
客户端和代理服务器进入由选定认证方法所决定的子协商过程
子协商过程结束后,客户端发送请求信息,其中包含目标服务器的IP地址端口
代理服务器验证客户端身份通过后与目标服务连接目标服务器经过代理服务器向客户端返回状态响应
连接完成后,代理服务器开始作为中转中转数据

SOCKS5代理- Tcp echo server

package main

import (
    "io"
    "log"
    "net"
    "time"
)

func main() {
    // 设置代理服务配置
    socks.SetDefaultProxy(socks.SOCKS5, "localhost", 1080)

    // 创建TCP服务器套接字
    serverSocket, err := net.Listen("tcp", "localhost:12345")
    if err != nil {
        log.Fatal("Failed to create server socket:", err)
    }

    // 监听客户端连接
    go func() {
        for {
            conn, err := serverSocket.Accept()
            if err != nil {
                log.Println("Failed to accept client connection:", err)
                continue
            }

            log.Println("New client connected")

            // 设置客户端套接字为非阻塞模式
            conn.SetDeadline(time.Now().Add(time.Hour))

            // 建立到目标服务器的连接
            target, err := socks.Dial(conn, "localhost", 1080)
            if err != nil {
                log.Println("Failed to establish connection to target server:", err)
                continue
            }

            // 将客户端数据转发目标服务器,并将目标服务器的响应回显给客户端
            buf := make([]byte, 1024)
            for {
                n, err := conn.Read(buf)
                if err != nil && err != io.EOF {
                    log.Println("Failed to read data from client:", err)
                    break
                }

                if n == 0 {
                    break
                }

                target.Write(buf[:n])
            }

            target.Close()
            conn.Close()
        }
    }()

    select {} // 阻塞程序,使其不会退出
}

代码创建了一个基于Go语言的SOCKS5代理TCP回显服务器,它监听本地端口12345上的连接,并接受客户端请求。一旦客户端连接成功,服务器将通过SOCKS5代理建立到目标服务器的TCP连接,并将客户端发送的所有数据直接发送回客户端。当客户端关闭连接时,服务器将关闭相应的连接。
请注意,该代码中的代理服务器配置使用默认localhost地址端口1080。如果您使用的是不同的代理服务器,请相应地更改这些值。此外,该代码中的套接字使用了非阻塞模式,这使得服务器可以处理多个客户端并发连接。

SOCKS5代理-请求阶段

SOCKS5代理是一种网络协议,用于在客户端和服务器之间建立一个代理连接。其请求阶段是SOCKS5代理通信中的重要阶段之一,主要包括以下步骤:
客户端发送SOCKS5命令:客户端首先向代理服务器发送一个SOCKS5命令,告诉代理服务器其要连接的目标服务器地址端口号
代理服务器进行身份验证:代理服务器收到客户端的SOCKS5命令后,会对客户端进行身份验证。如果客户端身份验证不通过,则代理服务器会拒绝建立连接。
代理服务器向目标服务器发送SOCKS5请求:如果客户端身份验证通过,则代理服务器会向目标服务器发送一个SOCKS5请求,告诉目标服务器其要连接的客户端地址端口号
目标服务器返回响应:目标服务器收到代理服务器的SOCKS5请求后,会向代理服务器返回一个响应,告诉代理服务器是否允许建立连接。
代理服务器将响应发送给客户端:代理服务器收到目标服务器的响应后,会将其发送给客户端,告诉客户端是否成功建立连接。
在请求阶段中,需要注意以下几点:
客户端必须支持SOCKS5协议,否则无法与代理服务器进行通信
代理服务器需要对客户端进行身份验证,以防止恶意用户利用代理服务器进行攻击
代理服务器需要将客户端的真实地址端口号告诉目标服务器,以便目标服务器能够正确地与客户端进行通信
目标服务器需要对代理服务器的SOCKS5请求进行响应,以告诉代理服务器是否允许建立连接。
总之,请求阶段是SOCKS5代理通信中非常重要的一步,需要仔细处理

其中需要使用插件

完整代码

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 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", ver)
	}
	addr := ""
	switch atyp {
	case atypIPV4:
		_, 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
}

将这段代码在goland运行,并在终端中输入curl --socks5 127.0.0.1 1080 -v http://www.baidu.com测试

 在浏览器上测试我们的SOCKS5服务器。


​​​​​​​

 

 

 

原文地址:https://blog.csdn.net/Y2483490891/article/details/131938679

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_44064.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注