GO语言工程实践及课后作业:实现思路、代码以及路径记录| 青训营

三个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 协议的第五个版本,较之前版本有更多的功能和安全性。 主要特点和用途:

  1. 代理功能:SOCKS5 协议允许客户端通过代理服务器进行网络连接。客户端将请求发送给代理服务器,代理服务器再代表客户端与目标服务器建立连接,从而实现客户端的网络访问。
  2. 支持 TCP 和 UDP:SOCKS5 协议支持 TCP 和 UDP 两种传输协议。客户端可以通过 SOCKS5 代理服务器连接到目标服务器的 TCP 或 UDP 端口,使得对于某些应用,如远程访问、P2P 等,使用 SOCKS5 代理成为可能。
  3. 身份验证:SOCKS5 支持用户名和密码的身份验证机制,客户端可以在连接代理服务器时提供用户名和密码,以便代理服务器验证用户身份的合法性。
  4. IPv6 支持:SOCKS5 协议支持 IPv6 地址,使得客户端可以通过代理服务器连接到 IPv6 地址的目标服务器。
  5. 防火墙穿越:SOCKS5 可以帮助用户绕过防火墙限制,实现跨境访问被封锁的网站或服务。
  6. 匿名性:SOCKS5 提供一定程度的匿名性,因为目标服务器只能看到 SOCKS5 代理服务器的 IP 地址,而无法直接获取到客户端的 IP 地址。 需要注意的是,SOCKS5 是一种基于应用层的协议,与 HTTP/HTTPS 等基于传输层的代理协议不同。它可以与各种应用程序一起使用,而不需要对这些应用程序进行额外的配置。由于其强大的代理功能和灵活性,SOCKS5 协议在许多场景下被广泛使用,包括网络隐私保护、翻墙、加速等应用。

SOCKS5工作原理

SOCKS5(Socket Secure 5)协议的工作原理涉及客户端、代理服务器和目标服务器之间的数据传输和身份验证。下面是 SOCKS5 协议的工作流程:

  1. 客户端发送连接请求:当客户端需要访问目标服务器时,它会向 SOCKS5 代理服务器发送连接请求。这个连接请求中包含目标服务器的地址和端口号,以及客户端需要使用的验证方式(如果需要身份验证)。
  2. 代理服务器进行身份验证(可选):如果客户端请求身份验证,代理服务器会验证客户端提供的用户名和密码。这可以提供一定程度的安全性和授权访问。
  3. 代理服务器与目标服务器建立连接:代理服务器收到客户端的连接请求后,它会代表客户端与目标服务器建立连接。
  4. 数据传输:一旦代理服务器与目标服务器建立连接,它会在客户端和目标服务器之间充当中间人。客户端发送的所有数据都将通过代理服务器转发到目标服务器,目标服务器的响应也会经过代理服务器传回客户端。
  5. 连接关闭:当客户端或目标服务器关闭连接时,代理服务器会关闭它与两者之间的连接,完成数据传输过程。 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并行执行两个翻译函数。

结果如下:

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记