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两个字段
2.2.2 在线词典-代码生成
①复制curl,这里是bash,如果是cmd的话会失败
②代码生成网址curlconverter.com/#go,粘贴后选择语言…
③将代码复制到编辑器中,能够正确编译与运行
- 创建请求及参数
- 设置请求头
- 发送请求
- 防止资源泄露 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很复杂,再次用到代码生成
将预览中的json数据复制,将生成go代码
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
报错:
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失败,再结束连接