这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
首先是课堂的内容
Go快速上手
变量
通过var声明变量,类型与其他语言类似,如string、int、float等。如果不指定类型,go语言可以推断类型
同时也能指定const类型
if-else
if-else语句没有括号(),但是必须加大括号{}
if num := 9; num < 0 {
//
}else if num < 10 {
//
}else {
//
}
循环
只有for循环,for循环中的三项可以任意忽略。
同样和if-else一样不需要小括号
for i := 7; i < 10; i ++ {
fmt.Println(i)
}
for {
if xxx {
break;
}
}
switch
switch后跟变量名,case后跟变量的情况。case后面可以不跟大括号
a := 2;
switch a{
case 1 : //
case 2 :
}
数组
很少使用,定义的方法:
var a [5]int
var b [5][5]int;
var c := [5]int{1, 2, 3, 4, 5}
切片
切片相当于vector,是不定长的数组,使用make创建。可以像数组一样去取值,也可以用append追加元素,但是必须赋值给原切片
s := make([]string, 3)
s = append(s, "d")
var c := make([]string, len(s))
map
同其他语言一样,是一种键值对的数据结构。golang里面的map是无序存放的,在输出它的时候会乱序输出
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
range
对于一个切片slice或者map来说,用range可以更使代码更简介
用range遍历时,第一个返回的值为索引,第二个返回的值为数据
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num;
if num == 2 {
fmt.Println("index :", i, ",num:", num)
}
}
m := map[string]string{"a" : "A", "b" : "B"}
for k, v := range m {
fmt.Println{k, v}
}
函数
用func声明,不同于其他语言的是名字在前,类型在后。并且可以返回两个变量
func 函数名 (变量a a的类型, 变量b, b的类型) (返回类型,可以返回两个值)
指针
相比C++指针的功能已经少很多,代表传入引用修改这个变量的值
结构体
类似C/C++,关键词是type
type user struct {
name string
password string
}
结构体方法:类似成员函数;实现的方法是将结构体变量放到函数前面,并加上小括号
func (u user) checkPassword(password string) bool {
return u.password == password
}
错误处理
函数的返回值有两个,第二个返回值返回错误信息
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")
}
字符串操作
举例说明:
a := "hello"
strings.Contains(a, "ll")
strings.Count(a, "l")
strings.HasPrefix(a, "he")
strings.HasSuffix(a, "llo")
strings.Index(a, "ll")
strings.Join([]string{"he", "llo"}, "-")
strings.Repeat(a, 2) // hellohello
strings.Replace(a, "e", "E", -1) //hEllo
strings.Splist("a-b-c", "-")
string.ToLower(a)
string.ToUpper(a)
len(a)
字符串格式化(Printf):可以加%v打印任何变量,使用%+v、%#v打印更详细
Json处理
在每个字段的首字母都大写的结构体的字段后面加上 'json : "字段名"'
type userInfo {
Name string
Age int `json:"age"`
Hobby []string
}
使用JSON.marshaler序列化,JSON.unmarshaler反序列化
时间处理
time.now() 获取当前时间,time.Date()构造带时区的时间, .UNIX()获取时间戳
在生成随机数时,需要“播种”,用到
rand.Seed(time.Now().UnixNano())
数字解析
f, _ := strconv.ParseFloat("1.234", 64) // 1.234
n, _ := strconv.ParseInt("111", 10, 64) // 111
n, _ := strconv.ParseInt("0x1000", 0, 64) // 4096
n2, _ := strconv.Atoi("123") // 整形123
实战
都按最终版本实现了一次
猜数游戏
用到的输入比较麻烦,是因为熟悉一下bufio这种方式
大致的流程:
// 生成随机数
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNum := rand.Intn(maxNum)
// 读入数据
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
guess, err := strconv.Atoi(input)
简化的读取方式:
var guess int
_, err := fmt.Scanf("%d", &guess)
标准化输入和输出不一样,输入不能使用%v
在线词典
v1:
利用网站,根据请求报文的cURL生成go代码
// 创建请求
client := &http.Clinet{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good","user_id":"632f1a87342fe300152e1e24"}`) // 请求报文的一些信息
req, err := http.NewRequset("POST", URL, data) // 请求报文的方法体,请求资源的信息
// 继续添加请求报文的字段信息,设置请求头
// ......
// 发起请求
resp, err := client.Do(req)
defer resp.Body.Close() // 会在函数结束时候执行
// 读取响应
bodyText, err := iotil.ReadAll(resp.Body) // 这里就是响应报文的信息了
v2:
需要让一个变量代替输入,需要用到JSON序列化
创建一个结构体:
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
用自定义的字段创建请求:
request := DictRequest{TransType : "en2zh", Source : "good", UserID : "xxxx"}
buf, err := json.Marshal(request)
var data = bytes.NewReader(buf)
代替上一段的创建请求
v3:
我们要解析响应报文,利用OkTools网站将json转换为go语言的结构体:
添加结构体之后,我们最后只需要将bodyText这个json字符串反序列化成该结构体的字段就行
var dictResponse DictResponse
err = json.unmarshal(bodyText, &dictResponse)
fmt.Printf("#v", dictRespnse)
这样输出的信息就有了比较清楚的信息:
v4:
最后我们想要任意输入,最后输出它的中文解释
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, ", US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
输入的话标准化输入、命令行输入都行
- 添加另一种翻译引擎:按照上面的方法添加百度翻译等
- 并行请求两个翻译引擎:使用goroutinue来完成
SOCKS5代理
浏览器与socks5代理建立TCP连接,代理再和真正的服务器建立连接。
分为四个阶段:握手阶段、认证阶段、请求阶段、relay阶段
握手阶段与简单的socket通信类似,在process函数里面实现逻辑
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)
}
}
认证阶段:
浏览器先给代理服务器一个包,这个包有三个字段:version:协议版本号;methods:认证的方法数目;每个method的编码。
实现auth认证函数,在process函数里调用
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
}
}
把版本号读出来:
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)
// readFill填充method
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
log.Println("ver", ver, "methond", method)
请求阶段:
浏览器发送一个包,有6个字段:
然后创建connect函数,在process函数里调用,代码和上面认证阶段读取报文差不多
读取版本号ver、Connect请求cmd、目标地址类型atyp:
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
ver, cmd, atyp := buf[0], buf[1], buf[3]
判断目标地址类型:
-
IPV4:
_, err = io.ReadFull(reader, buf) addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]) -
HOST:
hostSize, err := reader.ReadByte() host := make([]byte, hostSize) _, err = io.ReadFull(reader, host) addr = string(host) -
IPV6:暂不支持
最后读取端口:
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2]) // 大端字节序
relay阶段:
用net.dial建立一个TCP连接
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()
用io.Copy可以实现单向数据转发,双向需要启动两个goroutinue
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
完成后,打开两个终端,就完成了
go run chapter1/proxy/v4/main.go
curl --socks5 127.0.0.1:1080 -v http://www.qq.com
个人觉得proxy还是很有难度的,读取报文段的字节那里不容易掌握。 通过上述的三个例子,基本上实现了其他语言到go语言的转化