Go语言基础 | 青训营笔记

375 阅读4分钟

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

课堂讲授内容

  1. Go 语言基础知识,变量、控制结构、函数、字符串、分片、json编组和解码等;
  2. 一些库的相关介绍和实践,如fmt、time、math/rand等;
  3. 通过三个小项目实践介绍和巩固 Go 相关知识点,更多地集中在网络开发,顺带控制台输入、流、随机数生成等。

什么是 Go 语言?

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

基础语法

  1. 变量声明和赋值(两种方式)

    a. var 变量名 数据类型,如 var i int; b. :=,如 s := “Hello world.”

  2. switch 分支:(和C/C++不同)不需要 break,执行完一个分支,直接转到最后。

  3. 切片slice

    使用 make 创建切片,其他操作如 append: 切片等

    package main
    
    import "fmt"
    
    func main() {
    	arr := make([]int, 3)
    	arr[0] = 0
    	arr[1] = 1
    	arr[2] = 2
    
    	fmt.Println("arr:", arr)
    	arr = append(arr, 4)
    	fmt.Println("arr:", arr)
    
    	arrCopy := make([]int, len(arr))
    	copy(arrCopy, arr)
    	fmt.Println("arrCopy:", arrCopy)
    }
    
  4. 字符串处理

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	a := "hello"
    	fmt.Println(strings.Contains(a, "ll"))                // true
    	fmt.Println(strings.Count(a, "l"))                    // 2
    	fmt.Println(strings.HasPrefix(a, "he"))               // true
    	fmt.Println(strings.HasSuffix(a, "llo"))              // true
    	fmt.Println(strings.Index(a, "ll"))                   // 2
    	fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
    	fmt.Println(strings.Repeat(a, 2))                     // hellohello
    	fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
    	fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
    	fmt.Println(strings.ToLower(a))                       // hello
    	fmt.Println(strings.ToUpper(a))                       // HELLO
    	fmt.Println(len(a))                                   // 5
    	b := "你好"
    	fmt.Println(len(b)) // 6
    }
    
  5. json处理:json.Marshal() 序列化,json.MarshalIndent(a, "", "\t")json.Unmarshal(buf, &b) 反序列化

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type userInfo struct {
    	Name  string
    	Age   int `json:"age"`
    	Hobby []string
    }
    
    func main() {
    	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
    	buf, err := json.Marshal(a)
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(buf)         // [123 34 78 97...]
    	fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
    
    	buf, err = json.MarshalIndent(a, "", "\t")
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(string(buf))
    
    	var b userInfo
    	err = json.Unmarshal(buf, &b)
    	if err != nil {
    		panic(err)
    	}
    	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
    }
    

Go 语言实战案例

猜数字游戏

  1. 涉及知识:随机数(种子)、控制台输入

    a. 随机数(种子):随机数种子能够保证每次生成的随机数不一样。常用时间戳time.Now().UnixNano() 做种子。例:

    maxNum := 100
    rand.Seed(time.Now().UnixNano()) // 设置随机数种子,保证每次生成的随机数不一样
    secretNumber := rand.Intn(maxNum)
    

    b. 控制台输入:bufio.NewReader(os.Stdin)ReadString

  2. 完整示例:给指定长度的数组赋随机值,并输出。

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"math/rand"
    	"os"
    	"strconv"
    	"strings"
    	"time"
    )
    
    func main() {
    	fmt.Println("Please input length of array")
    	reader := bufio.NewReader(os.Stdin)
    	input, err := reader.ReadString('\n')
    	if err != nil {
    		fmt.Println("Error.")
    		return
    	}
    	// fmt.Println(input)
    	input = strings.Trim(input, "\r\n")
    	n, err := strconv.Atoi(input)
    	if err != nil {
    		fmt.Println("Error.")
    		return
    	}
    	var arr = make([]int, n)
    	for i := 0; i < n; i++ {
    		rand.Seed(time.Now().UnixNano())
    		arr[i] = rand.Intn(100)
    	}
    	fmt.Println(arr)
    }
    

简单在线词典

  1. 涉及知识点:网络传输、抓包、json 编组和解码

    a. 网络传输和抓包:Convert curl to Go

    • (demo项目)创建请求、发送请求和接受响应请求:用彩云小译进行抓包,“右击检查→点击翻译时在 Name 处找到 Request Method 为 POST 的 dict→右击 copy cURL(bash) 到Convert curl to Go 进行转换为 Go”

    b. json 编组和解码:json 转 Golang struct

    • (demo项目)“复制彩云小译检查中的 Response 中的内容到 json 转 Golang struct 进行 Go 结构体转换”

    • json 编组和解码

      • json 编组:json.Marshal(v) or json.MarshalIndent(v, prefix, indent) ,将 Go 数据结构转化为 json 格式。

        package main
        
        import (
        	"encoding/json"
        	"fmt"
        	"log"
        )
        
        type Movie struct {
        	Title  string
        	Year   int  `json:"released"`
        	Color  bool `json:"color,omitempty"`
        	Actors []string
        }
        
        func main() {
        	var movies = []Movie{
        		{Title: "Casablanca", Year: 1942, Color: false,
        			Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
        		{Title: "Cool Hand Luke", Year: 1967, Color: true,
        			Actors: []string{"Paul Newman"}},
        		{Title: "Bullitt", Year: 1968, Color: true,
        			Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
        		// ...
        	}
        
        	// 1.无格式化显示
        	//data, err := json.Marshal(movies)
        	//if err != nil {
        	//	log.Fatalf("JSON marshaling failed: %s", err)
        	//}
        	//fmt.Printf("%s\n", data)
        
        	// 2.格式化显示
        	data, err := json.MarshalIndent(movies, "", "	")
        	if err != nil {
        		log.Fatalf("JSON marshaling failed: %s", err)
        	}
        	fmt.Printf("%s\n", data)
        }
        
        /*
        输出
        1.无格式化
        [{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actors":["Steve McQueen","Jacqueline Bisset"]}]
        
        2.格式化
        [
                {
                        "Title": "Casablanca",
                        "color": true,
                        "Actors": [
                                "Steve McQueen",
                                "Jacqueline Bisset"
                        ]
                }
        ]
        
                        "released": 1967,
                        "color": true,
                        "Actors": [
                                "Paul Newman"
                        ]
                },
                {
                        "Title": "Bullitt",
                        "color": true,
                        "Actors": [
                                "Steve McQueen",
                        ]       "Jacqueline Bisset"
                }
        ]
        
        */
        
        • json 解码:json.Unmarshal() 将 json 格式转化为 Go 数据结构

        接上例

        var titles []struct{ Title string }
        if err := json.Unmarshal(data, &titles); err != nil {
        		log.Fatalf("JSON unmarshaling failed: %s", err)
        }
        fmt.Println(titles)
        
        • json TAG:omitempty 表示当Go语言结构体成员为空或零值时不生成该JSON对象(这里false为零值)

SOCKS5 代理

原理

socks5原理.png

  1. 步骤1:认证阶段 auth

    $ go run main.go

    $ curl --socks5 127.0.0.1:1080 -v [<http://www.qq.com>](<http://www.qq.com/>)

     func auth(reader *bufio.Reader, conn net.Conn) (err error) {
         ...
    	// +----+----------+----------+
    	// |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
        ...
    
    
  2. 请求阶段

    请求:ver、cmd、atyp

    响应:用到的不多

    func connect(reader *bufio.Reader, conn net.Conn) (err error) {
            ...
    	// +----+-----+-------+------+----------+----------+
    	// |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
    	...
    }
    
  3. relay 阶段

    两个 go routing 中 io.Copy() 相反,第一个是从用户传到底层服务器,第二个相反。

    func connect(reader *bufio.Reader, conn net.Conn) (err error) {
    	...
    
    	go func() {
    		_, _ = io.Copy(dest, reader)
    		cancel()
    	}()
    	go func() {
    		_, _ = io.Copy(conn, dest)
    		cancel()
    	}()
    
    	<-ctx.Done()
    	...
    }
    

其他参考资料

课后总结

  • 收获:第一次接触 Go 语言,感觉这是一个功能很强大的语言,带有丰富的库和接口能够简单地完成很多复杂的操作,对于并发的设计也非常简单,在诸多领域起着非常高的开发效率,老师的讲解大部分是能够理解的,配套的实践项目也巩固了自己的语言学习。
  • 疑难:对于 go routing 和并发设计的理解还不够深刻,以及一些网络知识有点模糊,争取课下继续深入学习。