go基础入门 | 青训营笔记

79 阅读4分钟

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

go语言基础

因为之前有过一点基础, 所以看起来还算轻松, 需要注意的点是json那里, 没有讲到的是channel和go routine, 应该会在后续设计

json

序列化

从网页端返回的数据一般多是json类型, 而go存储这些数据一般是创建对应的结构体

序列化结构体为json, 主要有两点需要注意的, 一是对应的数据名称, 必须大写(public), 而是序列化时不能出现循环调用

 type Message struct {
     Name string `name`
     Body string `body`
     Time int64 `time`
 }

另外, 结构体对应名称后方, 使用反单引号包围的string值, 与json中的key一一对应.

最终, 生成的数据, 通过json.Marshal(x)序列化为json数据

eg:

 m := Message{"Alice", "Hello", 1294706395881547000}
 b, err := json.Marshal(m) 
 fmt.Println(b) //{"Name":"Alice","Body":"Hello","Time":1294706395881547000}

反序列化

反序列化是输入json数据, 将其转化为对应的struct, 以便代码中进行调用, 因此, 需要使用第三方网站json转go结构体帮助生成对应结构体, 接着将json数据反序列化为结构体

 var m Message
 err := json.UnMarshal(jsonText,&m)

其中, jsonText[]byte([]uint8), 是抓取到的json数据

request请求与response响应

request请求头

与python写爬虫类似, 进行http请求时, 需要先设置请求头

而设置请求头有一个简便的方法, 便是浏览器抓取对应的请求头, 复制为curl(bash), 在第三方网站curlConverter转化为go代码,

eg:

 ​
 func setHeader(req *http.Request) {
     req.Header.Set("Accept", "application/json, text/plain, */*")
     req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
     req.Header.Set("Connection", "keep-alive")
     req.Header.Set("Content-Type", "application/json;charset=UTF-8")
     req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
     req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
     req.Header.Set("Sec-Fetch-Dest", "empty")
     req.Header.Set("Sec-Fetch-Mode", "cors")
     req.Header.Set("Sec-Fetch-Site", "same-site")
     req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.49")
     req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
     req.Header.Set("app-name", "xy")
     req.Header.Set("device-id", "")
     req.Header.Set("os-type", "web")
     req.Header.Set("os-version", "")
     req.Header.Set("sec-ch-ua", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
     req.Header.Set("sec-ch-ua-mobile", "?0")
     req.Header.Set("sec-ch-ua-platform", `"Windows"`)
 }
 ​

接着流程便与python类似, 值得注意的是, 需要先将请求头附带的信息, 通过相应的数据结构包装起来, 序列化为json数据, 再将json数据请求一起通过http.Clinet()发送出去

 type dictRequest struct {
     //只有public的变量可以被序列化
     TransType string `json:"trans_type"`
     Source    string `json:"source"`
     UserID    string `json:"user_id"`
 }
 ​
 //请求体携带的数据
 requestContext := dictRequest{TransType:"en2zh",Source:"test"}
 //将其序列化为json
 buf,_ := json,Marshal(requeset)
 //设置请求链接, 并将携带的数据转化为Reader流(存储数据, 当前读到的下标, 以及prevRune的下标)
 request,_ := http.NewRequest(method:"POST","https://www.example.com",bytes.NewReader(buf))
 setHeader(request)
 ​
 response,err := (&http.Client{}).Do(request)
 defer response.Close()
 ​
 body,_ := ioutil.ReadAll(response.Body)
 ​

ioutil.Reader(r io.Reader)io.Copy(dst Writer, src Reader) (written int64, err error)

从io.Reader中读取所有数据, 但是应当避免使用ReadAll, 主要因为性能问题, 再面对大文件时, 存在性能差, 可能oom, 因为底层存储是使用buf([]byte)存储, 因此面临扩容问题, 在面对大文件时, 扩容次数频繁, 造成性能下降.

扩容是指切片的扩容, 当切片长度小于1024时, 每次是翻倍扩容, 大于1024时, 每次时1/4扩容

面对大文件时, 推荐使用io.Copy()代替

ioutil.ReadAll()相比, io.Copy()实现了对数据的完整处理, 即从src中读出数据, 再将其写入到dst中, 而ReadAll仅仅时获取数据缓冲区的数据, 并且只能先将数据全部读出来, 才能使用数据

对于copy()而言, 若是底层实现了WriteTo或是ReaderFrom, 那么便省区了中间buf缓存的步骤, 数据直接从src写到了dst

并且copy()使用了固定的buffer作为临时缓冲区, 不会像ReadAll一样频繁扩容.

另外, 如果是读取出来的数据, 需要使用json解码, 可以省去io.Copy()这一步

 type Result struct {
     Msg string `json:"msg"`
     Rescode string `json:"rescode"`
 }
 func parseBody(body io.Reader) {
     var v Result
     err := json.NewDecoder(body).Decode(&v)
     if err != nil {
         return nil, fmt.Errorf("DecodeJsonFailed:%s", err.Error())
     }
 }

直接使用json.NewDecoder(body).Decode(&v), 更加提升性能

今天课程中的代码, 这一部分便可以使用json.NewDecoder(body).Decode(&v)优化

服务器代理

首先创建服务器对特定ip端口进行监听, 并在接收到请求时启动go routine处理请求

在go routine中, 对请求链接进行验证(身份验证), 即事先约定好的认证方式, 进行验证, 验证无误后, 对其进行连接

其中需要注意context.WithCancel(context.Background)

withCancel接受一个context并返回其子context和取消函数

新创建协程中传入子Context做参数, 且需要监控子Context的Done通道, 若收到消息, 则退出

当想要新协程结束时, 在外部调用cancel(), 即会向子context的Done通道发送消息

父协程的Context的Done()关闭时, 子ctx的Done()也会关闭

这里还需要学习, 目前了解的还不是很清楚, 因为接触的太浅, 对整个流程掌控很弱

参考链接

  1. Golang Context 包详解
  2. Go学习之ioutil.ReadAll注意事项
  3. 为什么Body数据为空? 解决方法与ioutil.ReadAll解析
  4. 为什么要避免在 Go 中使用 ioutil.ReadAll?
  5. Go语言标准包解析
  6. golang中的ioutil.ReadAll vs io.Copy