Go 实战案例 | 青训营笔记

61 阅读3分钟

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

今天课程介绍了三个go的实战案例,初步了解go的基本用法

生成随机数

  • 随机数生成 使用包:math/rand

直接使用rand.Intn生成整型随机数会一直打印相同的数字

需要在使用前用时间戳初始化随机数种子,因为具有相同值的种子设定会导致每次运行产生相同的随机序列。 对于不同的数字,使用不同的值进行种子,例如time.Now().UnixNano(),它产生一个不断变化的数字。

  • 用户的输入与输出 获取stdin, stdout, stderr文件:os包 -- os.Stdin
reader := bufio.NewReader(os.Stdin)
// 读取一行输入
input, err := reader.ReadString('\n')
// check err是否为nil
// 去掉换行符
input = strings.Trim(input, "\r\n")
// 转换成数字
guess, err := strconv.Atoi(input)

命令行在线词典

  • 抓包 使用 curlconverter.com/ 将从网页开发者工具中的相应curl代码转换为go
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) if err != nil { log.Fatal(err) }
// 设置请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
// 发起请求
resp, err := client.Do(req) 
if err != nil { log.Fatal(err) } 
// 读取响应
defer resp.Body.Close() 
bodyText, err := ioutil.ReadAll(resp.Body)
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
        log.Fatal(err)
}
fmt.Printf("%#v\n", dictResponse)
  • 检查返回状态是否正确
if resp.StatusCode != 200 {
        log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}

SOCKS5代理服务器

  • TCP echo server 监听端口: server, err := net.Listen("tcp", "127.0.0.1:1080")

接受请求: client, err := server.Accept()

启动子线程进行处理: go process(client)

具体处理-函数退出时关闭连接:defer conn.Close()

测试:nc命令 -- 直接和某个端口进行TCP连接

  • auth: 认证阶段
    • 报文格式
    // +----+----------+----------+
    // |VER | NMETHODS | METHODS  |
    // +----+----------+----------+
    // | 1  |    1     | 1 to 255 |
    // +----+----------+----------+
    // VER: 协议版本,socks5为0x05
    // NMETHODS: 支持认证的方法数量
    // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
    // X’00’ NO AUTHENTICATION REQUIRED
    // X’02’ USERNAME/PASSWORD
    
    • 返回确认
    // +----+--------+
    // |VER | METHOD |
    // +----+--------+
    // | 1  |   1    |
    // +----+--------+
    _, err = conn.Write([]byte{socks5Ver, 0x00})
    
  • connect:请求阶段
    • 报文格式
    // +----+-----+-------+------+----------+----------+
    // |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
    // +----+-----+-------+------+----------+----------+
    // | 1  |  1  | X'00' |  1   | Variable |    2     |
    // +----+-----+-------+------+----------+----------+
    // VER 版本号,socks5的值为0x05
    // CMD 0x01表示CONNECT请求
    // RSV 保留字段,值为0x00
    // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
    //   0x01表示IPv4地址,DST.ADDR为4个字节
    //   0x03表示域名,DST.ADDR是一个可变长度的域名
    // DST.ADDR 一个可变长度的值
    // DST.PORT 目标端口,固定2个字节
    
    • 返回确认
    // +----+-----+-------+------+----------+----------+
    // |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
    // +----+-----+-------+------+----------+----------+
    // | 1  |  1  | X'00' |  1   | Variable |    2     |
    // +----+-----+-------+------+----------+----------+
    // VER socks版本,这里为0x05
    // REP Relay field,内容取值如下 X’00’ succeeded
    // RSV 保留字段
    // ATYPE 地址类型
    // BND.ADDR 服务绑定的地址
    // BND.PORT 服务绑定的端口DST.PORT
    _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    
  • relay 阶段 用tcp协议往对应的域名+端口进行TCP连接
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
        return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()

浏览器到服务器的双向数据循环: 2个copy函数 context机制:检查任何一方的copy失败则cancel

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

//从用户的浏览器拷贝数值到io的服务器
go func() {
        _, _ = io.Copy(dest, reader)
        cancel()
}()
//从底层服务器拷贝到用户的浏览器
go func() {
        _, _ = io.Copy(conn, dest)
        cancel()
}()

<-ctx.Done()
  • 测试 chrome浏览器中SwitchyOmega插件