[Go语言基础-基础语法 | 青训营笔记]

78 阅读7分钟

[Go语言基础-基础语法 | 青训营笔记]

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

一、本课重点内容

  • 简介

  • 入门

    • 开发环境
    • 基础语法
    • 标准库
  • 实战

    • 猜谜游戏
    • 命令行词典
    • SOCKS5 代理

二、详细知识点介绍

2.1 简介

2.1.1 什么是 Go 语言

  1. 高性能、高并发的编程语言,与 C++ 和 Java 媲美;
  2. 语法简单易懂,风格类似于 C 语言(简化),学习曲线平缓;
  3. 丰富的标准库,降低学习和使用成本;
  4. 丰富的工具链;
  5. 静态链接;
  6. 快速编辑;
  7. 跨平台(Linux Windows MacOS);
  8. 垃圾回收;

2.1.2 使用 Go 语言的公司

2.1.3 使用 Go 语言

  1. 性能问题;
  2. C++ 不太适合在线 Web 业务
  3. 部署简单,学习成本低

2.2 入门

2.2.1 开发环境-安装 Golang

go.dev/

2.2.2 开发环境-配置集成开发环境

  • VSCode(免费)
  • GoLand(收费)
  • ...

2.2.3 基础语法-HelloWorld

2.2.4 基础语法-变量

强类型,可以根据使用的上下文自动确定类型

2.2.5 基础语法-if else

if 后面没有小括号;必须使用大括号,不能把 if 的语句写到同一行。

2.2.6 基础语法-for循环

Go 中没有 while 循环和 do-while 循环,只有 for 循环。 总共有三段,任何一段都可以省略。for 后同样没有小括号。

2.2.7 基础语法-switch

变量名并不需要小括号。 与 C 和 C++ 不同,Go 中的 switch 语句不需要加 break;功能更加强大,可以使用多种变量类型(如:字符串);可以取代 if-elseswitch 后不写变量,在 case 里面写条件分支

2.2.8 基础语法-数组

数组长度固定,现实中使用较少,更为普遍使用的是切片。

2.2.9 基础语法-切片

切片可变长度,可以在任意时刻更改长度。切片操作类似于 python。 make: 创造切片; append: 向切片中追加元素,必须把 append 的结果赋值给原切片; copy: 两个切片间拷贝数据;

2.2.11 基础语法-range

可以快速遍历数组和 map。第一个值是索引(key),第二个值是对应位置的值(value)。

2.2.12 基础语法-函数

Golang 中变量类型是后置的。函数可以返回多个值。如:第一个值返回结果,第二个值返回错误信息。

2.2.13 基础语法-指针

与 C 和 C++ 相比,Golang 中指针的操作非常有限。用途之一为对常用的参数进行修改。

2.2.14 基础语法-结构体与结构体方法

结构体是带类型的字段的集合。没有初始化的数值会被置为空值。

结构体可以作为函数的参数,有指针和非指针两种。

结构体方法类似于类成员函数,可以带指针和不带指针。

2.2.15 基础语法-错误处理

使用一个单独的字符串来返回错误信息。不同于异常,可以非常清晰的知道哪个函数发生了错误并用分支语句进行错误处理。

2.2.16 基础语法-字符串操作与字符串格式化

使用 strings 包。

fmt.Printf(): 可以使用 %v 打印任何类型的变量,%+v``%#v 可以打印更加详细的结构。

2.2.17 基础语法-JSON 处理

每一个单词的首字母大写,使用 json.Marshal 进行序列化,使用 json.Unmarshal 进行反序列化。

2.2.18 基础语法-时间处理

time.Now() 可以快速获取当前时间;

time.Date() 可以构造一个带时区的时间;

.sub() 可以对两个时间进行减法,得到时间段;

.Format() 对时间进行格式化(2006-01-02 15:04:05);

time.Parse() 解析成时间;

time.Unix() 获取时间戳。

2.2.19 数字解析

ParseInt()``ParseFloat() 可以用来解析字符串。

Atoi() 可以快速把十进制字符串转换为数字。

2.2.20 进程信息

os.args 获取进程运行时的一些命令行参数;

os.Getenv()``os.Setenv() 获取或写入环境变量;

三、实践练习例子

3.1 猜数字游戏

3.1.1 生成随机数

需要设置随机数种子,一般使用时间戳来初始化随机数种子,可以得到不同的随机数。

maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)

3.1.2 读取用户的输入输出

使用 bufio.NewReader 转化为一个只读的流。

ReadString 去读取一行,.Trim 将末尾的换行符去掉。

fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
	fmt.Println("An error occured while reading input. Please try again", err)
	continue
}
input = strings.Trim(input, "\r\n")

guess, err := strconv.Atoi(input)
if err != nil {
	fmt.Println("Invalid input. Please enter an integer value")
	continue
}
fmt.Println("You guess is", guess)

3.1.3 实现逻辑判断

使用分支语句进行判断。

if guess > secretNumber {
	fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
	fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
	fmt.Println("Correct, you Legend!")
	break
}

3.1.4 实现游戏循环

使用 for 循环直到猜测正确。

3.2 命令行词典

3.2.1 抓包

3.2.2 代码生成

curlconverter.com/#go

client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
    log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
    log.Fatal(err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
req.Header.Set("app-name", "xy")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
resp, err := client.Do(req)
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}
if resp.StatusCode != 200 {
    log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
    log.Fatal(err)
}

3.2.3 生成 request body

将 json 序列化,构造结构体。

type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}

3.2.4 解析 response body

3.2.5 打印结果

fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
    fmt.Println(item)
}

3.3 SOCKS5 代理实现

3.3.1 TCP echo server

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	for {
		client, err := server.Accept()
		if err != nil {
			log.Printf("Accept failed %v", err)
			continue
		}
		go process(client)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		b, err := reader.ReadByte()
		if err != nil {
			break
		}
		_, err = conn.Write([]byte{b})
		if err != nil {
			break
		}
	}
}

3.3.2 auth

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	log.Println("auth success")
}

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
	// +----+----------+----------+
	// |VER | NMETHODS | METHODS  |
	// +----+----------+----------+
	// | 1  |    1     | 1 to 255 |
	// +----+----------+----------+

	ver, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read ver failed:%w", err)
	}
	if ver != socks5Ver {
		return fmt.Errorf("not supported ver:%v", ver)
	}
	methodSize, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read methodSize failed:%w", err)
	}
	method := make([]byte, methodSize)
	_, err = io.ReadFull(reader, method)
	if err != nil {
		return fmt.Errorf("read method failed:%w", err)
	}
	log.Println("ver", ver, "method", method)
	// +----+--------+
	// |VER | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+
	_, err = conn.Write([]byte{socks5Ver, 0x00})
	if err != nil {
		return fmt.Errorf("write failed:%w", err)
	}
	return nil
}

3.3.3 请求阶段

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     |
	// +----+-----+-------+------+----------+----------+

	buf := make([]byte, 4)
	_, err = io.ReadFull(reader, buf)
	if err != nil {
		return fmt.Errorf("read header failed:%w", err)
	}
	ver, cmd, atyp := buf[0], buf[1], buf[3]
	if ver != socks5Ver {
		return fmt.Errorf("not supported ver:%v", ver)
	}
	if cmd != cmdBind {
		return fmt.Errorf("not supported cmd:%v", ver)
	}
	addr := ""
	switch atyp {
	case atypIPV4:
		_, err = io.ReadFull(reader, buf)
		if err != nil {
			return fmt.Errorf("read atyp failed:%w", err)
		}
		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
	case atypeHOST:
		hostSize, err := reader.ReadByte()
		if err != nil {
			return fmt.Errorf("read hostSize failed:%w", err)
		}
		host := make([]byte, hostSize)
		_, err = io.ReadFull(reader, host)
		if err != nil {
			return fmt.Errorf("read host failed:%w", err)
		}
		addr = string(host)
	case atypeIPV6:
		return errors.New("IPv6: no supported yet")
	default:
		return errors.New("invalid atyp")
	}
	_, err = io.ReadFull(reader, buf[:2])
	if err != nil {
		return fmt.Errorf("read port failed:%w", err)
	}
	port := binary.BigEndian.Uint16(buf[:2])

	log.Println("dial", addr, port)

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

	_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
	if err != nil {
		return fmt.Errorf("write failed: %w", err)
	}
	return nil
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	err = connect(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
}

3.3.4 relay 阶段

port := binary.BigEndian.Uint16(buf[:2])

dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
    return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()
log.Println("dial", addr, port)

_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
    return fmt.Errorf("write failed: %w", err)
}
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

四、课后个人总结

本课学习了 Go 语言的基础语法,从最基础的“Hello World” 到环境设置。

从个人感受来说,Go 语言与 C++ 较为类似,又有一部分 python 的特点。相较于 C++,Go 编译速度快,构建简单(不需要 makefile,CMake,ninjia 等等等等),引入第三方库简单(只需一个 import)。对于有语法基础的人来说,Go 语言简单易上手。本课程的三个实践(猜数字、命令行词典和 Socks5 代理实现)从不同的方面展示了 Go 语言的一些使用技巧。