Go 语言的实战案例 | 青训营笔记

110 阅读6分钟

Go 语言的实战案例 | 青训营笔记

  • 猜字谜游戏
  • 在线字典
  • SOCKS5 代理

猜字谜游戏

graph TD
1.生成随机数 --> 2.读取输入文本 --> 3.删除不必要的换行 --> 4.转换文本为数字 --> 5.循环判断是否猜数准确 --> 6.正确退出循环/错误返回第二步

猜字谜游戏代码

package main

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

func main(){
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNumber := rand.Intn(maxNum)
    //fmt.Println("The secret number issecretNumber)
        
    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)
        return
            continue
        }
        input = strings.TrimSuffix(input,"\n")
        
        guess,err := strconv.Atoi(input)
        if err != nil {
            fmt.Println("Invalid input. Please enter an integer value")
        return
            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.PrintIn("Correct, you Legend!")
            break
        }
    }
}

1.生成随机数

import"time"模块,rand.Seed(time.Now().UnixNano())该方法使用时间戳来初始化随机数种子,这样就会每次输出不一样要猜测的数据。若只有这secretNumber := rand.Intn(maxNum)一条代码,会导致每次只输出相同的数据。

2.读取输入文本

stdin文件用os.Stdin得到,直接操作这个文件非常不方便,所以使用reader := bufio.NewReader(os.Stdin)来转成一个只读的数据。

3.删除不必要的换行

input,err := reader.ReadString("\n')读取到一行,就发现会有不必要的换行符,input = strings.TrimSuffix(input,"\n")去掉换行。

4.转换文本为数字

guess,err := strconv.Atoi(input)转成数字,若转换成失败会弹出错误。

在线字典

graph TD
1.在网站上获得请求 --> 2.将请求转换为Go语言类型的程序 --> 3.将JSON字符串输入转换成JSON序列化 --> 4.整理数据

1.在网站上获得请求

通过彩云小译(caiyunapp.com),并对英语单词进行翻译,在网站中获得其请求。 29198f9e51b591cfbf59d1f6cd778d1.png

2.将请求转换为Go语言类型的程序

通过Convert curl to Go (curlconverter.com)将请求转换为Go语言类型,返回结果为JSON类型的字符串 req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)创建请求头

package main

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

func main() {
    client := &http.Client{}
    var data = strings.NewReader(`{"trans_type":"en2zh","source":"awesome"}`)
    //创建请求
    req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", 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", "5b0ce53c39e131ad4014c3044e87886b")
    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", `"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)
    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/113.0.0.0 Safari/537.36 Edg/113.0.1774.35")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    //发起请求
    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)
    }
    fmt.Printf("%s\n", bodyText)
}

3.将JSON字符串输入转换成JSON序列化

buf, err := json.Marshal(request)去序列化request数组,将固定的JSON字符串转变为JSON序列化

package main

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

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

func main() {
    client := &http.Client{}
    // var data = strings.NewReader(`{"trans_type":"en2zh","source":"awesome"}`)
    request := DictRequest{TransType: "en2zh", Source: "good"}
    buf, err := json.Marshal(request)
    if err != nil {
        log.Fatal(err)
    }
    var data = bytes.NewReader(buf)
}

将响应结果转化并保存在结构体变量dictResponse\#%v来打印dictResponse,用最详细的方式打印结构体

type DictResponse 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      []interface{} `json:"synonym"`
        Antonym      []interface{} `json:"antonym"`
        WqxExample   []interface{} `json:"wqx_example"`
        Entry        string        `json:"entry"`
        Type         string        `json:"type"`
        Related      []interface{} `json:"related"`
        Source       string        `json:"source"`
    } `json:"dictionary"`
}

func main() {
    // fmt.Printf("%s\n", bodyText)
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%#v\n", dictResponse)
}

4.整理数据

对内容进行处理,选择我们想要的信息进行输出

func main() {
    //对输出进行裁剪选择
    fmt.Println("awesome", "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
    for _, item := range dictResponse.Dictionary.Explanations {
        fmt.Println(item)
    }
}

SOCKS5 代理

b9ced782acfd8592602da0863b2d76a.png SOCKS5 的实现步骤分为以下四步:

  1. 协商阶段:客户端向代理服务器发送代理请求,其中包含了代理的版本和认证方式;
  2. 认证阶段:服务端收到客户端的代理请求后,选择双方都支持的加密方式回复给客户端,此时客户端收到服务端的响应请求后,双方握手完成,开始进行协议交互;
  3. 请求阶段:客户端向代理服务器发送请求,由代理服务器将请求转发给真正想要请求的服务器;
  4. relay阶段:客户端向代理服务器发送数据,由代理服务器将数据转发给服务器,并将服务器返回的响应结果发送给客户端。

代码实现

1.协商阶段: 让服务器监听 127.0.0.1:1080 网址端口 reader := bufio.NewReader(conn)将连接关掉,结束整个函数周期

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)
    for {
        b, err := reader.ReadByte()
        if err != nil {
            break;
        }
        _, err = conn.Write([]byte{b})
        if err != nil {
            break
        }
    }
}

2.认证阶段: 客户端向代理服务器发送类似注释中的数据,代理服务器对客户端发送的数据进行错误处理,如果没有错误,就返回认证信息

VERNMETHODSMETHODS
111 to 255
  1. VER: 协议版本,socks5为0x05

  2. NMETHODS: 支持认证的方法数量

  3. METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:

    • 0X00 不需要认证
    • 0X02 USERNAME/PASSWORD认证
func auth(reader *bufio.Reader, conn net.Conn) (err error) {

    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)
    }
    log.Println("ver", ver, "method", method)
 
    _, err = conn.Write([]byte{socks5Ver, 0x00})
    if err != nil {
        return fmt.Errorf("write failed:%w", err)
    }
    return nil
}

3.请求阶段: 在客户端和代理服务器之间建立了连接后,客户端就向代理服务器发送请求,由代理服务器将请求转发给真正想要请求的服务器,再由服务器返回响应

VERCMDRSVATYPDST.ADDRDST.PORT
11x'00'1Variable2
  1. VER 版本号,socks5的值为0x05
  2. CMD 0x01表示CONNECT请求 连接方式
  3. RSV 保留字段,值为0x00
  4. ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
    • 0x01表示IPv4地址,DST.ADDR为4个字节
    • 0x03表示域名,DST.ADDR是一个可变长度的域名
  5. DST.ADDR 一个可变长度的值
  6. DST.PORT 目标端口,固定2个字节
VERREPRSVATYPBND.ADDRBND.PORT
11X'00'1Variable2
  1. VER socks版本,这里为0x05
  2. REP Relay field,内容取值如下 X’00’ succeeded
  3. RSV 保留字段
  4. ATYPE 地址类型
  5. BND.ADDR 服务绑定的地址
  6. BND.PORT 服务绑定的端口DST.PORT
func connect(reader *bufio.Reader, conn net.Conn) (err error) {

    buf := make([]byte, 4)            //四个字节的缓冲区
    _, err = io.ReadFull(reader, buf) //先填充满buf,内容包括VER、CMD、RSV、ATYP
    if err != nil {
        return fmt.Errorf("read header failed:%w", err)
    }
    ver, cmd, atyp := buf[0], buf[1], buf[3]
 //对VER、CMD、ATYP进行错误处理
    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 {
    //根据ip地址的不同类型进行相应的处理
        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")
    }
    //读取端口号保存在buf中
    _, err = io.ReadFull(reader, buf[:2])
    if err != nil {
        return fmt.Errorf("read port failed:%w", err)
    }
    port := binary.BigEndian.Uint16(buf[:2])

    log.Println("dial", addr, 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)
    }
    return nil
}

4.relay阶段:

在请求阶段的代码中,添加服务器对客户端请求的数据的响应结果

func connect(reader *bufio.Reader, conn net.Conn) (err error) {
    
    ... //请求阶段代码
    
    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
}