实践文章-GO语言工程实践课后作业 | 青训营

72 阅读3分钟

GO学习

[TOC]

2-实战

2.1 猜谜游戏

生成随机数:

​ math/rand包

​ 法①rand.Intn(100) ——每次都会打印相同的数字

​ 法②需要设置随机数种子,一般用时间戳来初始化随机数种子rand.Seed(time.Now().UnixNano)

输入:

​ fmt.Scanf:与C中一样,需要使用占位符 fmt.Scanf("%d %f %s %t %c", &a, &b, &str, &c, &d)

​ fmt.Scanln:一行一行读取数据 fmt.Scanln(&a, &b, &str, &c)

​ fmt.Scan:fmt.Scan(&a, &b, &c, &str)

我的实现:

package main

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

func main() {
	rand.Seed(time.Now().UnixNano())	//随机数种子,时间戳
	answer := rand.Intn(100)	//上界为100生成随机数
	fmt.Println("Please input your guess:")
	var in int
	for {
		fmt.Scan(&in)
		if in > answer {
			fmt.Println("your number is BIGGER than the answer, please try again")
		} else if in < answer {
			fmt.Println("your number is SMALLER than the answer, please try again")
		} else {
			fmt.Println("Congrat! you are right!")
			break
		}
	}

}

2.2 命令行版本的词典

原理:调用第三方API去查询单词,并打印

学习:①如何使用go发送http请求 ②解析json ③如何使用代码生成来提高开发效率

2.2.1 在线词典-抓包

fanyi.caiyunapp.com/为例,右键-检查-网络…

请求的方式是json,参数是source,trans_type两个字段

屏幕截图 2023-07-31 135256.png

2.2.2 在线词典-代码生成

①复制curl,这里是bash,如果是cmd的话会失败

屏幕截图 2023-07-31 135813.png

②代码生成网址curlconverter.com/#go,粘贴后选择语言…

屏幕截图 2023-07-31 140040.png

③将代码复制到编辑器中,能够正确编译与运行

  • 创建请求及参数
  • 设置请求头
  • 发送请求
  • 防止资源泄露 defer
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
)

func main() {
	client := &http.Client{}	//可以指定很多参数
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)	//创建http的post请求,参数1为请求方式,参数2为url,参数3为data,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", "c1db820487afab8e68dd8f9ae59a6384")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	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)
	}
  //拿到response后,resp.Body仍然是一个流,为了防止资源泄露,手动添加defer去关闭这个流;函数结束后调用close函数
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)	//读取数据到内存
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText)	//打出来的应该是json字符串
}
2.2.3 在线词典-生成request body

序列化一个json:构造结构体,使其与json结构对应

type DictRequest struct{
	TransType string `json:"trans_type"`
	Source string `json:"source"`
}

new一个request;生成bytes数组;转换成data

	request := DictRequest{TransType: "en2zh", Source: "good"}
	buf, err := json.Marshal(request) //生成Bytes数组
	var data = bytes.NewReader(buf)   //由于buf是字节数组,因此此处换为bytes.NewReader()
2.2.3 在线词典-解析response body

将返回的response反序列化

需要写一个结构体,结构体字段与response一一对应,再把返回的json字符串反序列化到结构体中

由于返回的response很复杂,再次用到代码生成

oktools.net/json2go

将预览中的json数据复制,将生成go代码

屏幕截图 2023-07-31 164411.png

2.2.4 在线词典-最终实现

此处为我的实现

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
}

type DicResponse struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `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 main() {
	fmt.Println("Please enter your word:")
	var word string
	fmt.Scanln(&word)

	client := &http.Client{} //可以指定很多参数
	// var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)

	request := DictRequest{TransType: "en2zh", Source: word}
	buf, err := json.Marshal(request) //生成Bytes数组
	var data = bytes.NewReader(buf)   //由于buf是字节数组,因此此处换为bytes.NewReader()

	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) //创建http的post请求,参数1为请求方式,参数2为url,参数3为data,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", "c1db820487afab8e68dd8f9ae59a6384")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	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)
	}
	//拿到response后,resp.Body仍然是一个流,为了防止资源泄露,手动添加defer去关闭这个流;函数结束后调用close函数
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body) //读取数据到内存
	if err != nil {
		log.Fatal(err)
	}
	// fmt.Printf("%s\n", bodyText) //打出来的应该是json字符串

	var dicResponse DicResponse
	err = json.Unmarshal(bodyText, &dicResponse)
	if err != nil {
		log.Fatal(err)
	}
	// fmt.Printf("1\n%#v\n",dicResponse)

	fmt.Printf("%s UK:%s US:%s\n", dicResponse.Dictionary.Entry, dicResponse.Dictionary.Prons.En, dicResponse.Dictionary.Prons.EnUs)
	for _, item := range dicResponse.Dictionary.Explanations {
		fmt.Println(item)
	}
}

2.3 SOCKS5 代理

授权的用户可以用的单个窗口去访问内部所有资源

2.3.1 工作原理

浏览器与代理服务器建立TCP连接;代理服务器再与真正的服务器进行TCP连接。

1.协商阶段:浏览器->代理服务器发送报文,报文包括版本号、支持的认证种类。

1.1通过协商:代理服务器选择其支持的鉴权方式返回给浏览器,若为00则代表不需要认证。返回其他,下一步走认证流程。

2.发送请求:认证通过后,浏览器->代理服务器

2.1建立TCP连接:代理服务器->真正服务器

2.2返回响应:真正服务器->代理服务器

2.3返回状态:代理服务器->浏览器

3.发送数据:浏览器->代理

3.1relay数据:代理->真正服务器转发请求

3.2响应结果:真正->代理

3.3响应结果:代理->浏览器

代理服务器不关心流量的细节

2.3.2 TCP echo server

发送啥,回复啥

运行:nc 127.0.0.1 1080

报错:

屏幕截图 2023-07-31 183104.png

Windows安装nc命令-见其他笔记

VSCode安装:

package main
import(
	
	"bufio"
	"log"
	"net"
)

func main(){
	server , err := net.Listen("tcp","172.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)	//处理连接,go表示启动一个go routine,相当于启动一个子线程,但开销小很多,可轻松处理上万的并发

	}
}
func process(conn net.Conn){
	defer conn.Close()	//意思是函数退出时一定要把连接关掉
	reader := bufio.NewReader(conn)	//基于这个连接创建一个只读的带缓冲的流
	for{
		b, err := reader.ReadByte()	//每次读一个字节
		if err != nil{
			break
		}
		_, err = conn.Write([]byte{b}) //写入,正常写入的是slice这里一个字节的话用[]byte对其进行转化

		if err !=nil{
			break	//出错则break且关闭连接
		}
	}
}
2.3.5 auth 认证阶段
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
	// +----+----------+----------+
	// |VER | NMETHODS | METHODS  |
	// +----+----------+----------+
	// | 1  |    1     | 1 to 255 |
	// +----+----------+----------+
	// 浏览器向代理服务器发送一个报文,包括3个字段
	// 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
}
2.3.6 请求阶段
2.3.7 relay阶段

建立浏览器与服务器的双向数据转换

io.copy函数

需要等待任何一个方向的copy失败,再结束连接