这是我参与「第五届青训营 」伴学笔记创作活动的第 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的操作
JSON操作
实战案例——猜数游戏
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")
}
}
}
存在问题
用户往程序控制台进行输入,当出现高并发读写的时候,所有的线程不一定能处理过来,这时候就把请求收纳到缓冲区中;
使用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)
查看请求
①利用代码生成工具,将请求头转成代码
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
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协议
协商过程
客户端连到服务器后,然后就发送请求来协商版本和认证方法:
ver,nmethods分别占一个字节。
ver——协议版本,socks5为0x05
nmethods——支持认证的方法数
methods——方法列表
如05 02 00 01就说明ver为05,方法数两个,分别为00和01
然后socks5响应浏览器,告诉他选择那种方法
请求过程
- 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…