这是我参与「第五届青训营 」笔记创作活动的第1天
为什么选择Go?
腾讯、阿里、字节等互联网大厂纷纷转Go,Go语言到底有怎样的魔力呢?
- 编译快,开发和运行快
- Go是云时代的语言
- 简单易学
- 简单的并发
- 部署简单
Go语言基础
Hello,World
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, World!")
}
变量
变量类型
变量(Variable)的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。
Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。
变量声明
Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用。
变量的初始化
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。
for
for statement; condition expression; post statement {
// 循环内容
}
statement是单次表达式,循环开始时会执行一次这里
expression是条件表达式,即循环条件,只要满足循环条件就会执行中间循环体。
statement是末尾循环体,每次执行完一遍中间循环体之后会执行一次末尾循环体
执行末尾循环体后将再次进行条件判断,若条件还成立,则继续重复上述循环,当条件不成立时则跳出当下for循环
switch
当分支过多的时候,使用if-else语句会降低代码的可阅读性,这个时候,我们就可以考虑使用switch语句
- switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
- switch 语句在默认情况下 case 相当于自带 break 语句,匹配一种情况成功之后就不会执行其它的case,这一点和 c/c++ 不同
- 如果我们希望在匹配一条 case 之后,继续执行后面的 case ,可以使用 fallthrough
array
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。
slice
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
map
在 Go 语言中,map 是散列表的引用,map 的类型是 map[K]V ,其中 K 和 V 是字典的键和值对应的数据类型。map 中所有的键都拥有相同的数据类型,同时所有的值也都拥有相同的数据类型,但是键的类型和值的类型不一定相同。键的类型 K ,必须是可以通过操作符 == 来进行比较的数据类型,所以 map 可以检测某一个键是否存在。
point
指针也是一个变量,但它是一种特殊的变量,因为它存储的数据不仅仅是一个普通的值,如简单的整数或字符串,而是另一个变量的内存地址。
struct
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
struct-method
在 Go 语言中,结构体就像是类的一种简化形式,那么面向对象程序员可能会问:类的方法在哪里呢?在 Go 中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。
Go语言实战
猜谜游戏
目标: 输入一个数,如果比要猜的数大 输出Smaller,如果比要猜的小,输出Bigger,否则输出Correct
实现步骤:
- 生成随机数:需要用到
math/rand包,并且需要用时间戳初始化随机数种子 - 读入用户输入:课程中使用文件读入,需要用到
os包 - 实现判断逻辑,用
if实现 - 实现游戏循环,使用
for
完整代码如下:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
for {
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)
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
}
}
}
在线词典
目标: 实现一个在线词典
实现步骤:
- 抓包:找到需要调用接口的网站,先请求一次,然后去浏览器控制台中
network找到此次POST请求的相应信息 - 代码生成:右键请求,选择
Copy as cURL,接下来访问curlconverter.com/#go ,把复制的内容粘贴上去,就可以自动生成 相应的Go语言代码
3.生成Request Body:直接序列化即可
4.解析Response Body:使用oktools网站将JSON转化为Go Struct,然后反序列化即可
- 打印结果: range打印即可
SOCKS5
介绍
SOCKS5是一种代理协议,但是它是明文传输,不能用来翻墙。
用途是让用户通过单个端口访问内网所有资源
原理
如图所示,在Client在访问HOST时,先和Socks5 Server进行协商,然后再发送请求,请求与响应都由Socks5 Server来转发
实现步骤:
-
监听TCP echo server(
127.0.0.1:1080) -
使用server.Accept()接受请求拿到连接,然后进行处理
-
使用nc命令进行测试,
nc 127.0.0.1:1080 -
实现auth鉴权:首先客户端给服务端发送报文,内容如下:
然后,把字段读出来,回复报文如下:
5.代理请求阶段:先截获用户发送的报文:
然后回复报文:
然后与目标IP端口建立连接,然后用io.Copy进行双向数据转发,需要注意的是要用ctx阻塞进程