Day 1 初识go语言|青训营笔记

112 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

一、什么是go语言

1.go语言是一门高性能高并发的编程语言,标准库提供了对高并发的支持 2.语法简单,如只有一种for循环 3.标准库丰富,兼容性和稳定性大大提高 4.工具链丰富,内置了单元测试框架等等 5.静态编译 6.快速编译 7.跨平台 8.自带垃圾回收机制

二、入门go语言 -- 基础语法

go语言是强类型语言,其中字符串是内置类型

go语言变量声明方式(go语言中变量类型都是后置的): 1.var 变量名 (变量类型) = 值 2.变量名 := 值

go语言中的switch无须break 且case后可以使用任意变量类型,因此可以替代任意ifelse语句

//创建数组的两种方式
var array1 [3]int
array2 := [3]int{0,0,0}
复制代码

可使用make创建切片(slice即一中可变长数组)和map 创建语法:

s := make([]int,3)
m := make(map[stirng]int)
m["one"] = 1
r,ok := m["unknow"]
//加一个ok用于判断map中有没有"unknow"这个key存在
//前一个类型是key的类型,后一个是value的类型
//可通过方括号语法写入读取k-v对,用delete(m,"one")删除k-v对
//在map当中的数据是完全无序的
复制代码

对于一个slice或map可以使用快速遍历 range会返回两个值,一个是索引另一个是对应位置的值

go语言中的函数原生支持返回多个值

结构体:

type user struct{
    name string 
    password string
}

a := user{name :"czz", password :"111"} 
//没有初始化的字段是空值
复制代码

goland当中可以为结构体定义一些方法,类似于其它语言的类成员函数

错误处理: goland中使用一个单独的返回值来处理异常

字符串格式化: 可以使用%v作为占位符 %后加+可以输出字段名,加#可以输出结构体构造信息

JSON处理

buf, err = Json.Marshal()
//传入参数为一个结构体,即可将该结构体序列化为一个Json对象
Json.Unmarshal(buf,&_)
//将buf反序列化为一个结构体
复制代码

时间处理

now := time.Now()
//获取当前时间
now.Unix()
//获取时间戳
t := time.Date(2022, 5, 7, 16, 55, 36, 0, UTC)
//构造一个带时区的时间
复制代码

socks5服务器搭建

socks5代理流程

认证阶段:

浏览器/客户端向服务器发送认证报文,服务器做出响应


func auth(reader *bufio.Reader, conn net.Conn) (err error) {
   //认证阶段
   //认证报文包含三个字段:协议版本号,方法数,对于每个方法的描述
   //for {
   // b, err := reader.ReadByte()
   // if err != nil {
   //    break
   // }
   // _, err = conn.Write([]byte{b})
   // //将请求发送的数据进行打印
   // if err != nil {
   //    break
   // }
   //}
   ver, err := reader.ReadByte()
   if err != nil {
      return fmt.Errorf("read version 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 method size failed:%w", err)
   }
   //读入方法数
   methods := make([]byte, methodSize)
   _, err = io.ReadFull(reader, methods)
   if err != nil {
      return fmt.Errorf("read methods failed:%w", err)
   }
   //将methods读满
   log.Printf("ver %v methods %v", ver, methods)

   //读取完报文后,需要返回一个包,包括协议版本号和进程方式,0x00为免密进程,0x02为用户名密码进程
   _, err = conn.Write([]byte{socks5Ver, 0x00})
   if err != nil {
      return fmt.Errorf("write failed:%w", err)
   }
   //for item := range methods {
   //
   //}
   return nil
}
复制代码

连接阶段:

浏览器/客户端向socks5服务器发送一个tcp请求,服务器解析请求,并向目标ip发送请求, socks5服务器收到响应后,向客户端返回一个报文

//连接阶段
/*连接报文包括:
1.版本号
2.CMD 表示哪种请求,0x01表示connect请求
3.RSV保留字段,值为0x01,不用管
4.ATYPE 表示目标地址类型,DST,ADDR的数据对应这个字段的类型
   0x01表示IPv4地址,DST.ADDR为四个字节
   0x03表示域名,DST.ADDR是一个可变长度的域名
   若为域名,则第一个字节为域名长度
5.DST.ADDR 是一个可变长度的值
6.DST.PORT 目标端口,两个字节
*/
ver, err := reader.ReadByte()
if err != nil {
   return fmt.Errorf("read version failed:%w", err)
}
if ver != socks5Ver {
   return fmt.Errorf("not supported ver:%v", ver)
}
//读取版本号
cmd, err := reader.ReadByte()
if err != nil {
   return fmt.Errorf("read cmd failed:%w", err)
}
if cmd != cmdBind {
   return fmt.Errorf("not supported cmd:%v", cmd)
}
//读取cmd
_, err = reader.ReadByte()
if err != nil {
   return fmt.Errorf("read RSV failed:%w", err)
}
//读取RSV
buf := make([]byte, 4)
addr := ""
atype, err := reader.ReadByte()
if err != nil {
   return fmt.Errorf("read atype failed:%w", err)
}
switch atype {
case atypIPV4:
   _, err = io.ReadFull(reader, buf)
   if err != nil {
      return fmt.Errorf("read ip failed:%w", err)
   }
   addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeIPV6:
   return fmt.Errorf("IPv6 not supported now")
case atypeHOST:
   hostSize, err := reader.ReadByte()
   if err != nil {
      return fmt.Errorf("read host size 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)
default:
   return fmt.Errorf("not supported atype:%v", atype)
}

_, 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)
/*
   返回报文包括:
   1.版本号
   2.REP relay field 0x00表示成功
   3.RSV 保留字段
   4.ATYPE 地址类型
   5.BND.ADDR 服务绑定的地址
   6.BND.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)
}
复制代码

relay阶段:

socks5服务器与目标地址之间建立一个双向的数据传输通道

dest, _ := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
   return fmt.Errorf("dial dest failed:%w", err)
}
defer dest.Close()
复制代码

然后开启两个goroutine同时向目标服务器和客户端传送数据 直到有一方停止发送数据

ctx, cancel := context.WithCancel(context.Background())
go func() {
   io.Copy(dest, reader)
   cancel()
}()
go func() {
   io.Copy(conn, dest)
   cancel()
}()
<-ctx.Done()
return nil