这是我参与「第五届青训营 」伴学笔记创作活动的第 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)
- response反序列化 访问 oktools.net/json2go 将相应json结构体转换为go代码块
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插件