这是我参与「第五届青训营 」伴学笔记创作活动的第1天
标准库文档 studygolang.com/pkgdoc
课程示例项目 github.com/wangkechun/…, 克隆后进入目录运行go run example/01-hello/main.go, 输出hello world, 则说明环境配置正确
Go语言学习路线图⭐ bytedance.feishu.cn/docs/doccn3…
学习
Go模块代理 goproxy.cn/
1 Go语言简介
特点:
1 高性能, 高并发
2 语法简单, 学习曲线平缓
3 丰富的标准库
4 完善的工具链
5 静态链接
6 快速编译
7 跨平台
8 垃圾回收
为什么字节跳动全面拥抱Go语言
1 最初使用Python, 由于性能问题换成了Go
2 C++不太适合在线Web业务
3 早期团队非Java背景
4 性能比较好
5 部署简单, 学习成本低
6 内部RPC和HTTP框架的推广
总结: 上手简单, 性能高, 尝到甜头后, 全面推广
2 Go语言开发入门
开发环境配置
安装Golang (见go.dev)
Go模块代理 (见goproxy.cn)
IDE 我选Goland (Jet brain出品)
基础语法和标准库
1 switch
不写break也有break的效果
switch后的变量可以是任意类型
switch后可以不加变量, 以此代替if嵌套
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
2 map
创建
m := make(map[string]int)
读/增/改
m["one"] = 1
读方法2
r, ok := m["unknow"] // ok是bool, 可知道这个unknow这个key是否存在
删
delete(m, "one")
3 函数
原生支持返回多个值
实际业务逻辑代码中, 几乎所有函数都返回多个值, 第一个值是真正结果, 第二个值是错误信息
4 指针
功能有限, 主要用来修改传入的参数或是避免大结构体拷贝的开销
5 结构体
定义
type user struct {
name string
password string
}
声明并初始化
a := user{name: "wang", password: "1024"} // 没初始化的字段默认为零值
结构体方法
// 不带指针
func (u user) checkPassword(password string) bool {
return u.password == password
}
// 带指针
func (u *user) resetPassword(password string) {
u.password = password
}
// 调用
a.checkPassword("1024")
a.resetPassword("2233")
6 错误处理 习惯利用返回值处理错误, 不同于其它语言用异常处理 优点: 更清晰知道错误来源, 用简单的if else可处理
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err !- nil {
fmt.Println(err)
return
}
fmt.Println(u.name)
7 字符串操作
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
// 格式化
%v 可用于打印各个类型
%+v 打印详细结果
%#v 进一步详细
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
8 JSON处理 只有一个结构体的字段首字母都是大写, 就可以用json.Marshal进行序列化
type userInfo struct {
Name string
Age int `json:"age"` // 用这个tag可以让输出的字段名是age而不是Age
Hobby []string
}
// 序列化
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 97 103 ... ]
fmt.Println(string(buf)) // buf类型是[]byte
// 序列化 - 带缩进
buf, err = json.MarshalIndent(a, "", "\t")
// 反序列化
var b userInfo
err = json.Unmarshal(buf, &b)
9 时间处理
// 当前时间
time.Now()
// 自定义时间 time.UTC标准时间, time.Local当地时间
time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
// 格式化为字符串
fmt.Println(t.Format("2006-01-02 15:04:05"))
// 作差
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
// 字符串转Time, 第一个参数是格式
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
// 时间戳
now.Unix() // 1648738080
now.UnixMilli() // 毫秒
now.UnixMicro() // 微妙
now.UnixNano() // 纳秒
10 字符串转数字
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
// 第二个参数指定进制, 2到36, 若为0则根据字符串前置判断, 0x是16, 0是8, 否则是10
// 第三个参数指定结果必须是无溢出复制的整数类型, 64即int64
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
// 字符串转整型
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
11 进程信息
// 命令行参数, []string
os.Args
// 获取环境变量
fmt.Println(os.Getenv("PATH"))
// 设置环境变量
fmt.Println(os.Setenv("AA", "BB"))
// 执行命令并获取输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
3 Go实战 ⭐(含金量很高)
练习到很多知识点, 和实战工具
课程示例项目 github.com/wangkechun/…, 克隆后进入目录运行go run example/01-hello/main.go, 输出hello world, 则说明环境配置正确
项目1 猜谜游戏
知识点:
随机数生成
流程控制
输入输出
项目2 在线词典
知识点:
发送HTTP请求
解析JSON
借助代码生成提高效率(用curl生成go代码, 用json生成go struct)
项目3 SOCKS5
知识点:
SOCKS协议
goroutine
很多细节, 建议反复学习这个项目⭐
v1 - TCP echo sever
目的: 建立一个能返回信息的TCP sever
效果查看方法:
运行程序
然后cmd执行nc nc 127.0.0.1 1080建立起tcp连接
之后每发送字符串就会被echo
// nc安装
eternallybored.org/misc/netcat…
// 配置环境变量
blog.csdn.net/Zp_insist/a…
v2 - auth (认证阶段)
目的: 实现协议第一步认证阶段
(
核对VER, 是否为0x05, 即socks5
把NMETHODS, METHODS读掉
把VER, METHOD写入
(即_, err = conn.Write([]byte{socks5Ver, 0x00}))
)
效果查看方法:
运行程序
cmd执行curl --socks5 127.0.0.1:1080 -v www.qq.com
cmd显示Failed (因为还没实现完, 是正常的)
IDE中打印出了
2023/01/15 17:55:16 ver 5 method [0 1]
2023/01/15 17:55:16 auth success
v3 - 请求阶段
目的: (
读并校验VER和CMD字段(VER要为0x05即socks5, CMD要为0x01表示CONNECT请求)
读掉保留字段RSV
读掉ATYP
根据ATYP(0x01表示IPv4, 0x03表示域名)再去读DST.ADDR, IPv4读4个字节即可, 域名的话再读一个字节得到hostSize, 根据hostSize再去读
再读BND.PORT(端口号), 2个字节, 并转为整型
写响应报文
(_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}))
)
效果查看方法:
启动程序
cmd执行 curl --socks5 127.0.0.1:1080 -v www.qq.com
IDE打印
2023/01/15 18:16:45 client 127.0.0.1:14147 auth failed:IPv6: no supported yet
v4 - relay阶段
目的: 和真正的服务器建立tcp连接
效果测试方法:
运行程序
// -v 表示打印详细内容
然后cmd中执行curl --socks5 127.0.0.1:1080 -v www.qq.com
// qq不知道为什么不行, 访问百度就可以
curl --socks5 127.0.0.1:1080 -v www.baidu.com
使用Chrome的SwitchyOmega插件测试代理效果
新建情景模式
代理协议 SOCKS5
代理服务器 127.0.0.1
代理端口 1080
应用选项
然后在右上角切换为这个代理
IDE启动我们的代理服务器
此时流量会经过代理服务器, IDE里会log出来相关信息