《Go 语言上手-基础语言》实践课浅思 | 青训营笔记

229 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

主要针对《Go 语言上手-基础语言》中的实践部分进行学习和总结

GuessingGame

相关API

  • 随机数种子:rand.Seed(time.Now().UnixNano())

  • 控制台输入:

    • reader := bufio.NewReader(os.Stdin):获得buf的reader对象,

      input, err := reader.ReadString('\n'):根据某个delim作为输入的结束符,获得的字符串包含该结束符

      input = strings.TrimSuffix(input, "\n"):trim输入的字符串的后缀

      guess, err := strconv.Atoi(input):将输入转换为int类型

    • _, err := fmt.Scanf("%d", &guess):根据输入格式,格式化的获取输入,第二个参数是输入值的引用

SimpleDict

相关API

涉及的包:

  • http:提供HTTP的客户端和服务端的实现

    • cgi:实现了RFC3875协议描述的CGI
    • cookiejar:实现了内存兼容的http.CookieJar
    • fcgi:实现了FastCGI协议
    • httptest:提供HTTP测试的单元工具
    • httputil:补充http中的通用函数
    • pprof:提供HTTP服务返回runtime的统计数据,以pprof可视化工具规定的返回格式返回
  • ioutil:实现了一些I/O工具函数

  • log:logging

代码中API的使用:(仅记录代码中使用到的部分API)

  • client := &http.Client{}:创建一个HTTP的客户端,且是协程安全的,应当仅创建一次能够复用即可。

    type Client struct {
        Transport RoundTripper
        CheckRedirect func(req *Request, via []*Request) error
        Jar CookieJar
        Timeout time.Duration
    }
    

    Transport:接口,表示HTTP事务,处理客户端的请求并等待服务端的响应。若为nil,则使用DefaultTransport

    CheckRedirect:处理重定向的策略。若不为nil,客户端在执行重定向之前执行本函数,req和via是将要执行的请求和已经执行的请求(越新的越靠后)。若为nil,采用默认策略(10次连续请求后停止)

    Jar:制定cookie管理器,将相关的cookie插入到每个请求中,并用响应的cookie值进行更新。若为nil,则不发送cookie

    Timeout:计时器,包括连接、重定向、读取响应body的时间,会在client的方法返回后继续计时,并在超时后终端响应body的读取。设置为0表示不设置超时。

    client.Do(req):发起请求

  • http.NewRequest(method, url string, body io.Reader) (*Request, error)

    根据输入的method、url和body获取一个request对象。

    func NewRequest(method, url string, body io.Reader) (*Request, error) {
        return NewRequestWithContext(context.Background(), method, url, body) // 具体实现
    }
    

    Context:golang1.7发布的标准包,增强并发控制技术,即对多层结构的goroutine进行管理,实现当上层任务取消后,所有下层任务也会被取消,同时该上层任务更上层的任务不会被影响。

    context.Background():是顶层的context,通常被用于主函数、初始化、测试中

  • type Request struct {
        Method string
        URL *url.URL
        Proto      string
        ProtoMajor int    
        ProtoMinor int   
        Header Header
        Body io.ReadCloser
        GetBody func() (io.ReadCloser, error)
        ContentLength int64
        TransferEncoding []string
        Close bool
        Host string
        Form url.Values
        PostForm url.Values
        MultipartForm *multipart.Form
        Trailer Header
        RemoteAddr string
        RequestURI string
        TLS *tls.ConnectionState
        Cancel <-chan struct{}
        Response *Response
        ctx context.Context
    }
    

    Method:采用的HTTP方法

    URL:urlpkg.Parse(url)解析后的url对象

    Proto:HTTP协议,NewRequestWithContext(...)方法中采用HTTP/1.1

    Header:保存HTTP的头域,key大小写敏感,在客户端请求中会添加或重写Header中的特定字段

    type Header map[string][]string
    

    Body:请求主体,客户端中的Transport字段会负责调用Body的Close方法

    GetBody:可选函数,返回Body的复制,主要用于客户端重定向时,对Body的重新读取

    ContentLength:Body的长度,案例的代码中为data字符串的长度

    Close:客户端是否在发送请求后关闭连接;服务端是否在回复请求后关闭连接

    Host:主机名,格式为 host:port

  • strings.NewReader(s string) *Reader:strings包下的api,返回一个Reader,实现了io.Reader接口。只读且比bytes.NewBufferString更有效。

  • ioutil.ReadAll(r io.Reader) ([]byte, error):读取所有的数据,返回一个[]byte切片

  • json序列化:

    1. 定义结构体,结构体中包含需要的字段。

      其中,反引号中的代码表示,当编码为JSON字符串时,对应的key是冒号后面的字符串,需要用双引号包裹起来

    type DictRequest struct {
        TransType string `json:"trans_type"`
        Source    string `json:"source"`
        UserID    string `json:"user_id"`
    }
    
    1. 序列化json.Marshal(v interface{}) ([]byte, error):返回byte切片。
    2. 转换为Reader:采用bytes.NewReader(buf)形成io流,传入Request中。
  • json反序列化:

    1. 定义结构体:利用oktools.net/json2go将抓包得到的Response,转换为go的结构体。
    2. 反序列化json.Unmarshal(data []byte, v interface{}) error:传入byte类型切片和结构体的引用,反序列化结果写入v的结构体中。
    3. 利用结构体中的字段,实现后续的操作。

在线词典抓包流程

fanyi.caiyunapp.com/的抓包为例

  1. F12,在翻译框内输入翻译的例子,并刷新

  2. 在Network中找到dict文件,主要关注Headers,Payload,Preview,Response

    Headers:列出资源的请求url、HTTP方法、响应状态码、请求头和响应头及它们各自的值、请求参数等等

    Payload:Request Payload信息

    Preview:资源预览

    Response:响应信息面板包含资源还未进行格式处理的内容

  3. 右键dict文件,找到Copy as cURL(bash),复制curl命令行

  4. 打开curlconverter.com/#go,用于将cURL转换为golang代码。

  5. 复制到ide中运行,即可实现http的请求和响应结果的抓包。

Others

  • 防御式编程:

    if resp.StatusCode != 200 {
        log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
    }
    
  • curl命令:

    CommandLine URL,在Shell终端中利用命令行实现基于URL规则的文件传输,可上传和下载,支持HTTP、HTTPS、FTP等多种协议。

    格式:curl [param] url

  • 并发实现:

    • channel
    • waitGroup
    • errGroup
    • context
  • CGI:来源于http包中的cgi包

    www.zhihu.com/topic/19569…

    go-zh.org/pkg/net/htt…

SOCKS5 Proxy

基本原理

  • 定义:通过代理服务器在客户端和服务端之间交换数据

  • 位置:OSI模型中的会话层

  • 工作流程:

    参考:datatracker.ietf.org/doc/html/rf…

    jiajunhuang.com/articles/20…

    RFC:datatracker.ietf.org/doc/html/rf…

    replace/assets/images/socks5/client-socks5_f.jpg

    1. 初始连接:客户端和socks5代理建立TCP连接
    1. 握手阶段:包括协商阶段和子协商阶段(认证阶段)

      • 客户端发送如下数据

        +----+----------+----------+
        |VER | NMETHODS | METHODS  |
        +----+----------+----------+
        | 1  |    1     | 1 to 255 |
        +----+----------+----------+
        # 数字单位是byte,下同
        

        VER:协议版本,此处使用的是socks5,因此为0x05

        NMETHODS:客户端支持的认证方法的数量

        METHODS:方法值,即有多少方法就有多少byte,预定义的值的含义如下

        X’00’ NO AUTHENTICATION REQUIRED
        X’01’ GSSAPI
        X’02’ USERNAME/PASSWORD
        X’03to X’7F’ IANA ASSIGNED
        X’80to X’FE’ RESERVED FOR PRIVATE METHODS
        X’FF’ NO ACCEPTABLE METHODS
        
        • 代理返回响应,包含代理支持的认证方法
        +----+--------+
        |VER | METHOD |
        +----+--------+
        | 1  |   1    |
        +----+--------+
        
    2. 请求阶段

      • 客户端告知目标地址

        +----+-----+-------+------+----------+----------+
        |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+
        

        VER:版本号,同上

        CMD:具体操作

        • CONNECT:0x01连接
        • BIND:0x02端口监听,监听服务端的一个端口
        • UDP ASSOCIATE:0x03使用UDP

        RSV:保留位,值为0x00

        ATYP:目标地址类型

        • 0x01:IPv4
        • 0x03:域名
        • 0x04:IPv6

        DST.ADDR:目标地址的值,根据ATYP设置地址的字节数

        • IPv4:4bytes
        • IPv6:16bytes
        • 域名:第一个字节表示接下来有多少字节表示目的地址

        DST.PORT:目标端口号

      • 代理relay,收到客户端请求后并解析,再与目标服务器建立TCP连接

      • 代理返回状态

        +----+-----+-------+------+----------+----------+
        |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
        +----+-----+-------+------+----------+----------+
        | 1  |  1  | X'00' |  1   | Variable |    2     |
        +----+-----+-------+------+----------+----------+
        

        REP:状态码,表示连接状态,如下所示

        X'00' succeeded
        X'01' general SOCKS server failure
        X'02' connection not allowed by ruleset
        X'03' Network unreachable
        X'04' Host unreachable
        X'05' Connection refused
        X'06' TTL expired
        X'07' Command not supported
        X'08' Address type not supported
        X'09' to X'FF' unassigned
        

        BND.ADDR和BND.PORT:代理分配的用来连接目标服务器的ip地址和端口号。通常代理中包含多个ip,返回状态中的ip和端口与socks5代理的ip和端口不同。

    3. 数据传输阶段

相关API

涉及的包:

  • bufio:带缓存的 IO

  • context:实现Context类型

  • encoding:数据byte和文本表达的编解码

    • binary:数值和byte之间的编解码的转换
  • net:提供可移植网络的 IO 接口,包括TCP/IP、UDP、域名解析、Unix域socket

代码中API的使用:

  • net.Dial(network, address string) (Conn, error):连接到指定网络上的地址,用于客户端

    输入参数

    • network:"tcp"、"tcp4" (IPv4-only)、"tcp6" (IPv6-only)、"udp"、"udp4" (IPv4-only)、"udp6" (IPv6-only)、"ip"、"ip4"(IPv4-only)、"ip6" (IPv6-only)、"unix"、"unixgram"、"unixpacket"
    • address:

      对于TCP和UCP,格式为 "host:port",host是数值或可以解析为IP的地址,port是端口号数值或服务器名;若为ipv6,则host需要用[]括起来。

      对于IP,network后加:和协议值或协议名,address直接为 host

    返回值:通用面向流的网络连接,多协程将同时invoke其中的方法

    type Conn interface {
        Read(b []byte) (n int, err error)       // 1
        Write(b []byte) (n int, err error)      // 2
        Close() error                           // 3
        LocalAddr() Addr                        // 4
        RemoteAddr() Addr                       // 5
        SetDeadline(t time.Time) error          // 6
        SetReadDeadline(t time.Time) error      // 7
        SetWriteDeadline(t time.Time) error     // 8
    }
    
    1. 从连接中读取数据,并可以设置timeout

    2. 向连接写入数据,并可以设置timeout

    3. 关闭连接。任何阻塞的Read或Write操作会被接触阻塞并返回error

    4. 返回本地网络地址

    5. 返回远端网络地址

    6. 设置Read和Write在连接中的deadline,等同于7和8同时执行

      deadline是操作失败的绝对时间,而非阻塞的绝对时间

  • net.Listen(network, address string) (Listener, error):对外公开本地的网络地址,创建一个服务器

    输入参数:本地的network和address

    • network:"tcp"、"tcp4"、"tcp6"、"unix"、"unixpacket"

    • address:基本要求同上。

      对TCP,若host为空或者不明确,将监听所有可用的单播和任播地址

      若port为"0"或空,则会自动选择一个值作为port

    返回值:通用面向流的网络监听器,多协程将同时invoke其中的方法

    type Listener interface {
        Accept() (Conn, error)      // 1
        Close() error               // 2
        Addr() Addr                 // 3
    }
    
    1. 等待,并将下一个Conn返回
    2. 关闭Listener
    3. 返回Listern的网络地址
  • Addr:网络端点地址

    type Addr interface {
        Network() string // 网络名
        String() string  // ip地址
    }
    
  • func (b *bufio.Reader) ReadByte() (byte, error):返回Reader的一个byte的数据

  • func io.ReadFull(r Reader, buf []byte) (n int, err error):将len(buf)的数据从Reader中读入buf中,返回多少字节被复制

  • func (bigEndian) binary.BigEndian.Uint16(b []byte) uint16

    BigEndian:大端,即数据存储按照从高位到低位的方式

    本实践中是依靠网络数据byte流传输,因此数据存储为大端

    函数将byte数组转换为uint16

  • type Context interface {
        Deadline() (deadline time.Time, ok bool)
        Done() <-chan struct{}
        Err() error
        Value(key interface{}) interface{}
    }
    
    • Deadline():返回该context被撤销的时间
    • Done():返回一个channel,当channel关闭时表示该context被撤销。若返回nil,则表示该context永远不会被撤销。连续调用会返回相同的结果。

      • WithCancel()在调用cancel时关闭done;
      • WithDeadline()在截止时间过期时关闭done;
      • WithTimeout()在超时结束时关闭done。
    • Err():若Done没有关闭,则返回nil;否则,返回non-nil

    • Value(key interface{}):根据key返回和该context相关的value,通过WithValue设置

  • func context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    接收一个Context,返回其子Context及其取消函数

  • func io.Copy(dst Writer, src Reader) (written int64, err error)

    (一个死循环)从src中复制内容到dst,直到遇到EOF或error,返回复制的byte数和error

Others

  • nc命令:netcat,任意TCP和UDP连接和监听

    格式:nc [options] destination port

  • curl --socks5 127.0.0.1:1080 -v http://www.qq.com命令
  • SwitchyOmega:Chrome浏览器中的代理扩展程序,用于管理和切换多个代理设置

总结

以上是我对《Go 语言上手-基础语言》中三个实践题目的思考,如有错误,敬请批评指正!