这是我参与「第五届青训营」伴学笔记创作活动的第 1 天
在这之前我学习过go语言基础,但是通过今日课程的学习,我又收获了很多以前没有注意到的知识细节,以下就是我对本堂课的笔记,如有不对的地方,还请友友们多多指点。
一、课程主要内容
今天的课程主要介绍了
- go语言开发环境;
- go语言的一些基础语法以及标准库的内容:比如go变量、循环、数组、切片、指针、结构体、json、fmt等内容;
- 三个go语言项目,分别是:猜字谜、在线词典和SOCKS5代理。
二、知识点回顾
go环境搭建
对于这部分内容我就不多说了,小白的话建议去搜一下相关博客,下载go安装包并配置环境。对于集成开发环境,我使用的是goland。
go基础语法与标准库
- var变量
go语言声明变量的方法:
- var a int 这种方式声明的变量默认值为0。
- var a int=100 这种方式属于声明并初始化。
- var a=100 这种方式声明的变量,go语言会自动根据“=”后面的内容来匹配类型。
- a:=100 这种方式是我最常用的方式,也是最简单的方式。
- 对于常量就是将var替换成const,例如:const a=120;但是常量是在后面用到时才自动匹配类型的(这一点是我以前没有注意到的)。
- for循环
go语言中没有while循环,只有for循环。通过for中的条件来实现不同循环的意义。比如实现死循环,那就不加条件。
- if条件判断
需要注意的就是条件是不需要加“()”的。
- switch分支
go的switch相比于其他语言的switch是非常方便的。不需要case后加break,他匹配实现完分支就会自动跳出。对于多个if,我们可以使用switch-case来代替,更方便。
- array数组
- 数组的声明方式:
var a [6]int
a:=[5]int{1,2,3,4,5}
var twoa [2][3]int
- 对于数组,我们可以通过len(a)获取数组a的长度;对于遍历数组可以使用嵌套for循环。
- slice
切片声明: a:=make([]string,3)
- 跟数组一样,我们可以通过len()的方式获取切片的长度。
- 对于切片,我们是可以动态改变容量的,使用append(“原切片”,“追加的内容”)来向切片中追加元素,为什么要用这种方式呢?是因为切片在追加的时候是会重新分配一块内存的,但是为了可以追加到原切片上就要使用这种方法来覆盖原切片。
- a[2:5]意思是从下标索引为2到下标索引为4的内容,注意不包括下标为5!!
- a[:5]意思是从下标为0到下标为5,不包括下标为5
- a[2:]意思是从下标为2到最后,注意包括下标为2
- map
map是key-value的形式,特别需要注意的是map存放的是随机的。 可以通过r, ok := m["unknow"]来验证map是否存在。
- range
for-range可以用来遍历,具体实例如下:
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
- func
格式:
func 函数名 (参数)返回值{
函数内容
}
- point
go语言中没有引用传递,只有值传递。所以我们要想修改一个值时就必须使用指针,类似于c++,但是像slice、map、chanl这种本来就是引用类型的,就不用指针了。 示例代码如下:
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
- struct
go语言中的struct就相当于java语言中的类。声明如下方式:
type user struct {
name string
password string
}
我们可以通过a:=user{name:"XXX",password:"xxx"}的方式来创建所谓的对象。
- struct-method结构体方法
其实就是和java中的类方法一样。格式如下:
func (X struct类型)方法名(参数)返回值{
内容
}
- error
go语言的error,非常方便,我们可以通过errors.New()方法来创建一个错误,方便维护。
- string
string包提供了无比强大的方法,比如通过string.Replace()来方法来代替原来string串中的内容。我通过这堂课才知道string.Index()方法还可以返回某个字符在string串中的下标。
- fmt
fmt包其实就是打印输出的包,使用Printf()方法可以格式化打印输出。在go语言中%v就代表打印全部内容,%+v可以打印出字段名,%#v还可以打印出详细到具体的包名。
- json
json.Marshal()方法进行序列化,结果为十六进制的东西。通过json.MarshalIndent()方法来变成json格式。json.Unmarshal()为解析json方法。
- time
区别于其他语言的不同,go语言format时间用的是2006-01-02 15:04:05。go语言通过time.Now().Unix()来获得时间戳。笔记写到这里我想到一个与时间戳有关的内容,就是生成随机数。这在下面的项目中会做出介绍。
- strconv
strconv包提供字符串转成int类型,以及int类型转成string类型等的方法。最常用到的是Atio。
- env
本堂课设计到的方法: 方法一:os.Args用于获取通过命令行传入的参数 方法二:os.Getenv获取环境变量;Setenv设置环境变量 方法三:exec.Command执行命令
go项目实战
生成随机数游戏:
这个项目需要注意到的就是,获取随机数需要通过随机种子,然而这个随机种子还必须是不固定的,这样才能获得真的随机数。代码如下:
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(100)
对于获取用户的输入,我们可以使用bufio.NewReader的系列方法,也可以直接使用fmt.Scanf。
在线词典
流程如下:
- 一、抓包
打开彩云翻译的网页,然后右键检查打开浏览器的开发者工具。
- 二、代码生成
找到dict的post请求,右键选择copy as curl,之后粘贴到终端就可以看到一大串的json然后copy到代码生成器中,直接生成go语言代码。
- 三、解析response body
打开oktools网站,将json粘贴进去,生成对应的结构体。
- 四、打印结果
用for range循环迭代,直接打印结构。
- 五、完善代码
之后我们就通过main来判断一下命令和参数的个数是否为两个,获取到用户输入的单词,直接进行翻译。
SOCKS5代理
原理图如下(来自课堂截图)
实现流程:
- -TCP echo server简单测试第一个tcp服务器
- -auth 编写auth函数,实现认证阶段
- -请求阶段 实现一个和auth函数类似的connect函数
- -reply阶段 直接用net.dial建立一个tcp连接
三、实践练习代码例子
对三个项目实战对于我来说还是比较难的,代码我就粘到这里了,以后慢慢细致研究
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
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)
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
}
}
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
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)
}
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
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])
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基础语法部分就是查漏补缺的状态,好多以前没有注意到的,通过这堂课来巩固基础。对于项目实战部分,第一个猜字谜没有难度,但是到第二个第三个就很难理解了,究其原因其实就是我对于这方面的基础还是不够。希望自己有时间还要仔细研究研究。今天很充实,继续加油!