Go基础和实战案例 | 青训营笔记

89 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

基础语法

变量

声明变量的两种方式 go能够进行自动类型推导 法一

var a = "hello"
var b int = 123

法二

a := "hello"

变量的默认值 当一个变量被声明后,如果没有显式地给它赋值,系统自动赋初始值

  • 整型和浮点型变量的默认值为0和0.0
  • 字符串变量的默认值为空字符串
  • 布尔型变量默认为false
  • 切片、函数、指针变量的默认为nil

常量

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

//const identifier [type] = value
const pai = 3.1415 

循环的另一种写法for-range

str := "abc.ok"
for index, val := range str {
    fmt.Printf("index=%d, val=%c \n", index, val)
}

如果我们的字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在utf8编码是对应3个字节。
此时需要将str转成rune切片

str2 := []rune(str)
for i := 0; i < len(str2); i++ {
    fmt.Printf("%c \n", str2[i])
}

数据类型的转换

通过strconv转换int和string

数组

创建数组的三种方式,推荐使用第二种

var numsArray01 [3]int = [3]int{1,2,3}
var numsArray02 = [3]int{1,2,3}
var numsArray03 = [...]int{6,7,8}

切片

创建切面的方式 法一——直接引用数组

var intArr = [5]int{1,2,3,4,5}
//表示slice引用到intArr数组中的[1, 3)元素
slice := intArr[1:3]

法二——通过make创建

//var name []type = make([]type, len, [cap])
//len大小。cap切片容量,可选
var slice = make([]int,5)
fmt.Println(s[2:5])
fmt.Println(s[:5])
fmt.Println(s[2:])

法三——

var slice = []string{"a", "b","c"}

切片的底层实现

  • slice是一个引用类型
  • slice是一个结构体
type struct slice {
    ptr *[2]int//指向数组
    int len
    int cap
}

切片的扩容——append函数

s = append(s, "e", "f")

append底层原理

  • 切片append操作的本质就是对数组扩容
  • go底层会创建一下新的数组newArr(安装扩容后大小)
  • 将slice原来包含的元素拷贝到新的数组newArr
  • slice重新引用到newArr

map

m := make(map[string]int)
m["one"] = 1
val, error := m["unknow"]
delete(m, "one")
for key, value := range m {
    fmt.Println(key, value)
}

结构体

值类型,因此需要在方法中修改结构体内的值需要传入指针

标准库strings——对string的操作

image.png

JSON操作

image.png

实战案例——猜数游戏

rand.Intn(N) 函数返回 0-N 之间的随机数,但rand.Intn () 函数是个伪随机函数,不管运行多少次都只会返回同样的随机数,因为它默认的资源就是单一值,所以必须调用 rand.Seed (), 并且传入一个变化的值作为参数,如 time.Now().UnixNano() , 就是可以生成时刻变化的值

func main() {
   rand.Seed(time.Now().UnixNano())
   var target = rand.Intn(100)
   for {
      var num int
      fmt.Scanln(&num)
      if num == target {
         fmt.Println("yes")
         break
      } else if num > target {
         fmt.Println("too big")
      } else {
         fmt.Println("too small")
      }
   }
}

image.png

存在问题 用户往程序控制台进行输入,当出现高并发读写的时候,所有的线程不一定能处理过来,这时候就把请求收纳到缓冲区中; 使用bufio.NewReader(os.Stdin)可以建立缓冲区,并把数据从控制台拿到缓冲区); 使用ReadString() 方式把数据从缓冲区拿到程序中,判断数据中的是否存在报错,有错误交给Err()处理并输出报错信息,而正确的字符串则提取出来给程序去使用。 改进后

func main() {
   rand.Seed(time.Now().UnixNano())
   var target = rand.Intn(100)
   var reader = bufio.NewReader(os.Stdin)
   for {
      //以分隔字符方式读取,遇到传入的分割字符时就返回结果,返回的结果包含分隔字符本身,返回的类型为string,比如传入\n,代码遇到\n字符就返回,而文件行尾都是以\n结尾,所以ReadString('\n')就实现了分行读取
      var intput, err = reader.ReadString('\n')
      if err != nil {
         fmt.Println("any error,please try again", err)
         continue
      }
      //删去结尾的\n
      intput = strings.TrimSuffix(intput, "\n")
      //字符转成数字
      var num, err = strconv.Atoi(intput)
      if err != nil {
         fmt.Println("Invalid input")
         continue
      }
      if num == target {
         fmt.Println("yes")
         break
      } else if num > target {
         fmt.Println("too big")
      } else {
         fmt.Println("too small")
      }
   }
}

实战案例——在线字典(发送http请求,解析json)

查看请求 image.png

image.png

①利用代码生成工具,将请求头转成代码
copy as cURL(bash)
curlconverter.com/go/

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

func main() {
	client := &http.Client{}
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("authority", "api.interpreter.caiyunai.com")
	req.Header.Set("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
	req.Header.Set("app-name", "xy")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"`)
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("sec-ch-ua-platform", `"Windows"`)
	req.Header.Set("sec-fetch-dest", "empty")
	req.Header.Set("sec-fetch-mode", "cors")
	req.Header.Set("sec-fetch-site", "cross-site")
	req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText)
}

②构造请求体 输入一个变量,而不是一个json,因此用一个结构体代替原来的json


type DictRequest struct {
   TransType string `json:"trans_type"`
   Source    string `json:"source"`
}

var request = DictRequest{"en2zh", "good"}
var buf, err = json.Marshal(request)
if err != nil {
   log.Fatal(err)
}
var data = bytes.NewReader(buf)

③解析响应,将json转为结构体(反序列化)
返回的数据非常复杂,所以要定义结构体会非常繁琐,所以要用到工具 oktools.net/json2go image.png

var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
   log.Fatal(err)
}
fmt.Println("%#v\n", dictResponse)

实战案例——SOCKS5代理服务器

①一个简单的 TCP echo server

func main() {
	// 建立 tcp 服务
	listen, err := net.Listen("tcp", "127.0.0.1:9090")
	if err != nil {
		fmt.Printf("listen failed, err:%v\n", err)
		return
	}
 
	for {
		// 等待客户端建立连接
		conn, err := listen.Accept()
		if err != nil {
			fmt.Printf("accept failed, err:%v\n", err)
			continue
		}
		// 启动一个单独的 goroutine 去处理连接
		go process(conn)
	}
}

func process(conn net.Conn) {
   // 处理完关闭连接
   defer conn.Close()
   // 针对当前连接做发送和接受操作
   for {
      reader := bufio.NewReader(conn)
      var buf [128]byte
      n, err := reader.Read(buf[:])
      if err != nil {
         fmt.Printf("read from conn failed, err:%v\n", err)
         break
      }

      recv := string(buf[:n])
      fmt.Printf("收到的数据:%v\n", recv)

      // 将接受到的数据返回给客户端
      _, err = conn.Write([]byte("ok"))
      if err != nil {
         fmt.Printf("write from conn failed, err:%v\n", err)
         break
      }
   }
}

②SOCKS5协议

image.png

协商过程

客户端连到服务器后,然后就发送请求来协商版本和认证方法:
image.png
ver,nmethods分别占一个字节。
ver——协议版本,socks5为0x05
nmethods——支持认证的方法数
methods——方法列表
如05 02 00 01就说明ver为05,方法数两个,分别为00和01
然后socks5响应浏览器,告诉他选择那种方法
image.png

请求过程

image.png

  • ver——版本号
  • CMD——0x01表示CONNECT请求
  • RSV——保留字段值为0x00
  • RSV——保留字段
  • ATYP——目标地址类型
    • 0x01——ipv4,DST.ADDR为4个字节
    • 0x03——域名,DST.ADDR为可变长度的域名,第一个字节为长度,后面n个字节为域名
  • DST.ADDR——地址或域名
  • DST.PORT——目标端口,固定两个字节

引用参考

[1].blog.csdn.net/qq_40734758…
[2].www.bilibili.com/video/BV1qA…