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

106 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

Day2

Go 语言的实战案例

课程源码

视频

PPT

本节课将通过详解猜数字、在线词典、Socks5代理三个实际案例,帮助大家从入门到实操,轻松上手 Go 语言。

猜数字

程序随机生成一个0~100的数字,用户根据程序的反馈(”大了“、“小了”)调整自己的猜想,直到猜对那个数字。

package main

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

func main() {
        maxNum := 100
    	// 用时间戳来初始化随机数种子
    	// 计算机的随机数字是伪随机且可复现的,所以每次运行都需要设置一个不同的随机种子。
        rand.Seed(time.Now().UnixNano())
        secretNumber := rand.Intn(maxNum)
        // fmt.Println("The secret number is ", secretNumber)

        fmt.Println("input your guess")
    	// 从标准输入读取用户输入
        reader := bufio.NewReader(os.Stdin)
        for {
            	// 读取直到第一次遇到'\n'字节,返回一个包含已读取的数据和'\n'字节的字符串,如"50\n"
                input, err := reader.ReadString('\n')
            	// 基操勿烦
                if err != nil {
                        fmt.Println("error. try again", err)
                        continue
                }
            	// 将前后端所有"\r\n"包含的utf-8码值都去掉,也就是去掉'\r'和'\n'
                input = strings.Trim(input, "\r\n")
            	// 将字符串转换成数字,反过来有Itoa()
            	// Atoi是ParseInt(s, 10, 0)的简写,10进制,0代表int。
            	// Itoa是FormatInt(i, 10) 的简写,返回i的10进制的字符串表示。
                guess, err := strconv.Atoi(input)
                if err != nil {
                        fmt.Println("enter an integer value")
                        continue
                }
                fmt.Println("You guess is", guess)
                if guess > secretNumber {
                        fmt.Println("Your guess is bigger")
                } else if guess < secretNumber {
                        fmt.Println("Your guess is smaller.")
                } else {
                        fmt.Println("Correct!")
                        break
                }
        }
}

在线词典

成品展示

go run main.go hello

hello UK: [ˈheˈləu] US: [həˈlo]

int.喂;哈罗

n.引人注意的呼声

v.向人呼(喂)

自动生成demo

第三方词典API——彩云小译

F12查看浏览器Network下dict的Headers、Payload、Preview。

右键dict Copy as cURL --> Convert curl commands

// 1. 创建请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", str_json_data)
// 2. 设置请求头
req.Header.Set("Connection", "keep-alive")
// 3. 发起请求
resp, err := client.Do(req)
defer resp.Body.Close()
// 4. 读取响应
bodyText, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(bodyText, &dictResponse)

构造request.body

// post_json_data 改用结构体
// strings.NewReader(json_str)
bytes.NewReader(json.Marshal(struct))

解析response.body

JSON转Golang Struct

json.Unmarshal(ioutil.ReadAll(resp.Body), &structResponse)
// fmt.Fprintf(os.Stderr, `usage: xxx`)

Socks5代理

三个阶段:握手、认证、请求、relay(接力、转发)。

v1:握手

process():echo请求。

# 不能用curl
nc 127.0.0.1 1080

echo

echo

v2:认证

auth():约定认证方式。

v3:请求

connect():接收local请求并回复local已接收成功。

v4:转发

connect():+请求remote,响应local。

curl --socks5 127.0.0.1:1080 -v https://www.qq.com
// main():调用process()
// conn: proxy上来自local的连接
conn, err := server.Accept()

// process():调用auth()、connect()
// reader: proxy读取来自local的数据
reader := bufio.NewReader(conn)

// connect():请求、转发
// dest: proxy发出到remote(addr:port)的连接
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
// 接通目的ip,回包,通知local请求转发成功。
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})

// 转发阶段
// context包提供上下文机制在 goroutine 之间传递 deadline、取消信号(cancellation signals)或者其他请求相关的信息。
// context.WithCancel 函数能够从 context.Context 中衍生出一个新的子上下文并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文以及它的子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号。
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()	// 幂等,可多次调用,但实际上没有意义

// io.Copy(dst Writer, src Reader) (written, err)
// 从src读取数据,并写入到dst
go func(){
    // 读取reader的数据并写入dest,即转发请求
    _, _ = io.Copy(dest, reader) 
    cancelFunc() // 出错的时候调用(不太理解这说法)
}()

go func(){
    // 读取dest的数据并写入conn,即转发响应
    _, _ = io.Copy(conn, dest) 
    cancelFunc() // 出错的时候调用(不太理解这说法)
}()
// 等待ctx执行完成,也就是等待cancelFunc执行
<-ctx.Done()