简单实战案例|青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 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,复制下来
在这个网址粘贴刚才复制的请求,选择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打印结构体可查看详细信息,打印部分属性即可输出需要的信息,打印翻译信息时可能有多条翻译,可用循环按行打印
细节完善
- 可添加判断返回状态码resp.StatusCode是否为200成功,反之说明出错
- 将查询过程单独写为自定义函数,将写死的单词改为传进来的参数world变量
- 在主函数里输入单词,调用查询函数传参,实现功能
- 全程注意处理函数返回的错误信息
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.协议版本 2.支持认证的方式数量 3.对应methods的长度
- 读取版本号
- 读取methodsize
- 用methodsize创建缓冲区,用io.ReadFull(只读流,缓冲区)将缓冲区填充满
- 打印日志
- 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