三个go语言工程实践
一、猜谜游戏
首先设置一个随机种子,并利用随机种子生成随机数,如果这里不用随机种子的话,会导致每次生成的随机数都是一样的。然后用reader := bufio.NewReader(os.Stdin)
来创建了一个新的读取器(Reader),这个读取器会与标准输入流(os.Stdin
)关联。通过这个读取器,我们可以以更高级别的方式从标准输入中读取数据。
然后使用for循环,input, err := reader.ReadString('\n')
用来读取输入数据,将输入数据与生成的随机数进行比较,如果较小/大就输出该数较小/大的提示,并重新输入,直到输入的数与生成的随机数一致为止,这样就完成了一个小小的猜谜游戏。
但是在这里会遇到一个问题,该输入函数是一个读取缓冲区的函数,它会将输入的数字加上最后一个换行符一起作为字符串读入,所以在这里需要对input
进行一下处理,首先是要将换行符用strings.Trim(input, "\r\n")
函数去除掉,然后用strconv.Atoi(input)
将字符串转换成数字类型。
go
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)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\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
}
}
二、命令行词典
简介
实现一个命令行词典,在命令行输入一个英文单词时,返回英文单词的中文意思以及音标。具体实现是通过调用第三方的api对单词进行翻译并且打印出来。在这里将会学习如何用go语言发送http请求,解析json,以及如何提高开发效率。
要用到的api是彩云科技的在线翻译。fanyi.caiyunapp.com/
实现步骤
1.抓包
可以看到在翻译这个动作完成的时候,发送了一个http请求,并且得到的结果存在response里。
2.代码生成
在翻译网页上可以copycURL到网站 curlconverter.com/go# 中,会自动生成由go语言写成的发起http请求的代码。
3.解析response body
在浏览器得到的response的json,会结构非常复杂,所以可以用到 oktools.net/json2go/ 网站来将彩云小译页面的response里得到的json粘贴进去,点击转换嵌套就能转换成go结构代码,所得到的代码不需要其余的操作。
代码展示及注释
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
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 {
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"`
}
func query(word string) {
client := &http.Client{} //创建一个http client,这个函数还可以指定最大的返回时间
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request) //设置一个request结构体,将json序列化
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
//生成一个http请求
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() //按照go的习惯,需要手动关闭resp流,defer的意思是在函数结束后关闭,释放内存
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 { //得到的response不一定正确,需要检测一下返回的状态码
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse) //构造一个与json相同的结构体,并将json序列化到这个结构体变量里面
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)
}
三、SOCKS5代理
简介
SOCKS5介绍
SOCKS5 (Socket Secure 5) 是一种网络协议,用于在客户端和服务器之间传输数据,特别适用于代理服务器。它是 SOCKS 协议的第五个版本,较之前版本有更多的功能和安全性。 主要特点和用途:
- 代理功能:SOCKS5 协议允许客户端通过代理服务器进行网络连接。客户端将请求发送给代理服务器,代理服务器再代表客户端与目标服务器建立连接,从而实现客户端的网络访问。
- 支持 TCP 和 UDP:SOCKS5 协议支持 TCP 和 UDP 两种传输协议。客户端可以通过 SOCKS5 代理服务器连接到目标服务器的 TCP 或 UDP 端口,使得对于某些应用,如远程访问、P2P 等,使用 SOCKS5 代理成为可能。
- 身份验证:SOCKS5 支持用户名和密码的身份验证机制,客户端可以在连接代理服务器时提供用户名和密码,以便代理服务器验证用户身份的合法性。
- IPv6 支持:SOCKS5 协议支持 IPv6 地址,使得客户端可以通过代理服务器连接到 IPv6 地址的目标服务器。
- 防火墙穿越:SOCKS5 可以帮助用户绕过防火墙限制,实现跨境访问被封锁的网站或服务。
- 匿名性:SOCKS5 提供一定程度的匿名性,因为目标服务器只能看到 SOCKS5 代理服务器的 IP 地址,而无法直接获取到客户端的 IP 地址。 需要注意的是,SOCKS5 是一种基于应用层的协议,与 HTTP/HTTPS 等基于传输层的代理协议不同。它可以与各种应用程序一起使用,而不需要对这些应用程序进行额外的配置。由于其强大的代理功能和灵活性,SOCKS5 协议在许多场景下被广泛使用,包括网络隐私保护、翻墙、加速等应用。
SOCKS5工作原理
SOCKS5(Socket Secure 5)协议的工作原理涉及客户端、代理服务器和目标服务器之间的数据传输和身份验证。下面是 SOCKS5 协议的工作流程:
- 客户端发送连接请求:当客户端需要访问目标服务器时,它会向 SOCKS5 代理服务器发送连接请求。这个连接请求中包含目标服务器的地址和端口号,以及客户端需要使用的验证方式(如果需要身份验证)。
- 代理服务器进行身份验证(可选):如果客户端请求身份验证,代理服务器会验证客户端提供的用户名和密码。这可以提供一定程度的安全性和授权访问。
- 代理服务器与目标服务器建立连接:代理服务器收到客户端的连接请求后,它会代表客户端与目标服务器建立连接。
- 数据传输:一旦代理服务器与目标服务器建立连接,它会在客户端和目标服务器之间充当中间人。客户端发送的所有数据都将通过代理服务器转发到目标服务器,目标服务器的响应也会经过代理服务器传回客户端。
- 连接关闭:当客户端或目标服务器关闭连接时,代理服务器会关闭它与两者之间的连接,完成数据传输过程。 SOCKS5 协议的优势在于其支持 TCP 和 UDP 两种传输协议,以及身份验证功能。这使得 SOCKS5 协议在代理网络连接方面非常灵活和强大。它可以与各种应用程序一起使用,而不需要对这些应用程序进行额外的配置,因为它位于应用层而不是传输层。 SOCKS5 协议的这种工作方式允许用户绕过防火墙限制,实现跨境访问被封锁的网站或服务,并提供一定程度的网络匿名性,因为目标服务器只能看到 SOCKS5 代理服务器的 IP 地址,而无法直接获取客户端的 IP 地址。
本代码实现效果
启动这个程序,然后在浏览器里面配置使用这个代理,此时我们打开网页。 代理服务器的日志,会打印出你访问的网站的域名或者IP,这说明我们的网络流量是通过这个代理服务器的。 我们也能在命令行测试我们的代理服务器,我们可以用 curl-socks5 + 代理服务器地,后面加一个可访问的 URL,如果代理服务器工作正常的话,那么curl命令就会正常返回。
实现思路
1.实现一个简单的 TCP echo server
这个协议主要的功能是,给它发送什么,它就会返回什么。
2.实现一个认证阶段
读取认证报文的各个字段
3.实现请求阶段
读取请求报文的各个字段,判断目标地址类型,然后给一个回包,回包大部分字段都没有用,第二个REP字段填0表示成功,第四个ATYPE字段填1表示ipv4,后面字段都不用,这样connection阶段就完成了。
4.实现replay阶段
net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
建立tcp连接,用 defer.Close()
关闭连接。
用两个goroutine执行io.copy()
实现双向数据传输。第一个是从用户浏览器拷贝到底层服务器,另一个是从底层服务器拷贝到浏览器。
这里会有一个问题,就是启动这两个goroutine是几乎不耗时间的,这时会导致函数直接进行到最后然后返回,连接就会被关闭。所以这里要用到一个标准库里的context机制,用WithCancle()
函数等待任何一个方向的copy失败,才返回函数,并且关闭连接。
课后作业
一、修改猜谜游戏里面的最终代码,使用 fmt.Scanf 来简化代码实现
go
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)
fmt.Println("Please input your guess")
var guess int
for {
fmt.Scan(&guess)
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
}
}
}
二、修改命令行词典里面的最终代码,增加另一种翻译引擎的支持
选择的是百度翻译,网址是 fanyi.baidu.com/ 翻译结果如下:
三、在二的基础上,实现并行请求两个翻译引擎来提高响应速度
在 Go 语言中,你可以使用 goroutines 来实现并行请求两个函数。Goroutines 是 Go 中的轻量级线程,可以并发执行多个函数或任务。使用go标准库sync
里的var wg sync.WaitGroup
并行执行两个翻译函数。
结果如下: