简单实战案例|青训营笔记

78 阅读5分钟

简单实战案例|青训营笔记

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

案例详情代码见文末链接,此文章更适合作为笔记或大纲

1. 猜谜游戏

math/rand包

直接使用rand.Intn(范围) 创建的随机数每次都是一样的,需要设置随机数种子

用时间戳来初始化随机数种子

rand.Seed(time.Now().UnixNano())

读取一行输入,但会多读一个换行符(输入方式较为复杂但后续能用到,可用scanf输入替代)

reader:=bufio.NewReader(os.Stdin)

input,err :=reader.ReadString('\n')

用TrimSuffix去掉换行符

input=strings.TrimSuffix(input,"\n")

将输入的字符串转换为数字

guess,err :=strconv.Atoi(input)

实现判断逻辑,比较输入的数字和该随机数的大小,并输出相应提示。

用循环重复游戏进行,防止输入一次之后程序退出,当输入错误,弹出提示之后用continue继续游戏,当输入正确后break退出循环

注意返回错误的函数需要接受错误信息,并且用if判断是否有错误

2. 命令行词典

利用彩云科技提供的翻译API,按F12进入到网络,然后随便翻译一个单词,查看post请求的dict的负载和预览以及请求头。

右键请求,选择copy->copy as curl,复制下来

curlconverter.com/#go

在这个网址粘贴刚才复制的请求,选择go生成go代码

之后进行代码编写,

V1简单框架

创建请求
http.NewRequest(请求方式,地址,data)

三个参数分别是method(请求方式),url(地址),data(数据流)

设置请求头

也就是刚才生成的go代码,如果生成的代码有编译错误删除错误代码即可

发起请求

发起请求并接受响应

defer一下关闭请求得到响应流

defer为golang关键词,他会在函数退出前执行,defer的执行顺序可以理解为栈的顺序,先进后出,详情可自行查询,此处可理解为在函数退出前执行关闭响应流操作

读取响应

将读取的响应流转换为byte数组,打印即可

V2具体完善

创建单词结构体

可用json:字段将字段名统一,新建结构体变量,初始化字段,调用json.Marshal将结构体序列化成byte数组的json字符串,利用bytes.NewReader将数组转化成数据流,将数据流作为参数传到发起请求函数中。

创建响应结构体

利用oktools.net/json2go将翻译api中的响应格式(F12预览中看到的相应数据)转换成go代码的结构体

新建结构体变量,利用反序列化json.Unmarshal(上步打印的返回的byte数组的json字符串,结构体变量地址) 将返回的数据赋值到新建的结构体变量上

%#v打印结构体可查看详细信息,打印部分属性即可输出需要的信息,打印翻译信息时可能有多条翻译,可用循环按行打印

细节完善
  1. 可添加判断返回状态码resp.StatusCode是否为200成功,反之说明出错
  1. 将查询过程单独写为自定义函数,将写死的单词改为传进来的参数world变量
  2. 在主函数里输入单词,调用查询函数传参,实现功能
  3. 全程注意处理函数返回的错误信息

3. SOCKS5代理服务器

客户端和socks5代理服务建立tcp连接,代理服务器再和真正访问的服务器建立连接

代码详情见后文链接

1.简单的TCP echo server

监听端口接收一个server
新建请求接收连接

在死循环里用server新建一个请求并接收返回的连接

go process(连接)

go关键字可以理解为启动一个子线程,go可轻松处理上万并发

process函数的实现

defer 关闭连接

基于连接创建只读缓冲流bufio.NewReader(连接)

在死循环里用reader.ReadByte(会一次读取1kb缓冲流)每次读取一个字节,写到控制台上

go run运行

nc 127.0.0.1 1080

输入hello即可收到服务器返回的hello

2. 实现协议认证阶段

创建auth函数,参数为只读流和tcp连接

将process函数死循环删掉改为调用auth函数,注意处理错误信息,

认证阶段逻辑

  1. 浏览器给代理服务器发送报文 1.协议版本 2.支持认证的方式数量 3.对应methods的长度
  2. 读取版本号
  3. 读取methodsize
  4. 用methodsize创建缓冲区,用io.ReadFull(只读流,缓冲区)将缓冲区填充满
  5. 打印日志
  6. conn.Write([]byte{版本号,建成方式}) (构造的返回包)

3. 请求阶段

创建connect函数和auth函数类似,签名一致,在process里auth后调用

报文格式参数见代码,读取每个报文字段,并且验证其合法性,不同类型,读取长度不同,用switch分别处理,将字段按照其长度限制填充到新建的字节数组buf里

打印日志即可

按照协议格式创建回包conn.Write(见代码)

4. 和真正服务器建立tcp连接

dest,err:=nte.Dial("tcp",ip:端口)函数建立tcp连接

defer dest.Close() 结束前关闭连接

建立双向数据转换

go 两个函数,函数体内利用io.Copy,一个从浏览器拷贝数据到服务器,一个从底层服务器拷贝到浏览器

需要等待数据copy失败才能终止,否则go的速度过快,数据拷贝没有完成就会结束

WithCancel等待ctx结束才能退出函数,在go出错时调用cancel()函数,任何一个方向的copy失败函数退出,关闭双方连接,清理数据

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

运行代码

输入命令测试

curl --socks5 127.0.0.1:1080 -v www.qq.com

参考链接

案例代码:hi-hi.cn/go

案例代码:GitHub - wangkechun/go-by-example