这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。
主要卡在win vscode下的环境配置和小项目运行过程中的遇到的问题。
win vscode 配置go以及相关插件
难点:vscode下go插件的安装需要配置代理 go版本 1.18.1
终端输入以下命令后,vscode安装插件成功,如果仍然fail可能是网络问题。
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
go的便利之处:
- case上直接加条件 代替多个if else elseif else
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
- 比起数组 更常用切片-长度可变数组
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
- for循环可用range 遍历数组、map,类似python
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
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
}
}
- 输出使用%v 不限制值类型
小项目二:在线词典
便捷的代码生成工具:curlconverter.com/#go (win需要安装curl)
dict copy cURL在win中用的是“Copy as cURL(bash)” ,powershell的复制到代码生成工具里会报错。
请求的序列化: 原:请求用字符串表示,请求序列化——结构体+json.Marshal->byte数组
字符串:
var data = strings.NewReader(`{"text":"good","language":"en"}`)
序列化:
request := DictRequest_volc{TransText: word, Language: "en"}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
type DictRequest_volc struct { //转换不成功可能是空格换行
TransText string `json:"text"`
Language string `json:"language"`
}
解析response得到的json格式数据,然后反序列化到结构体中。不同翻译器词典结果可能不同
彩云:
火山
api返回json结构复杂,结构体不好自己定义,借助工具代码生成oktools.net/json2go(控制台输出json格式结果,整块复制到转换工具中,并注意去掉换行)
注意反序列化到结构体中时,需要加&
根据结构体打印输出:
// fmt.Printf("%s\n", bodyText)
if resp.StatusCode != 200 { //保证访问顺利
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse_volc DictResponse_volc
err = json.Unmarshal(bodyText, &dictResponse_volc)
if err != nil {
log.Fatal(err)
}
// fmt.Println(word, "UK:", dictResponse_volc.Dictionary.Prons.En, "US:", dictResponse_volc.Dictionary.Prons.EnUs)
for _, Word := range dictResponse_volc.Words {
fmt.Println(word, "UK:", Word.PosList[0].Phonetics[0].Text, "US:", Word.PosList[0].Phonetics[1].Text)
for _, pos_list := range Word.PosList {
for _, explan := range pos_list.Explanations {
fmt.Println(explan.Text)
}
}
}
两种query的并行运行:
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1]
WaitGroup := sync.WaitGroup{}
WaitGroup.Add(2)
go func() {
query_caiyun(word)
WaitGroup.Done()
}()
go func() {
query_volc(word)
WaitGroup.Done()
}()
WaitGroup.Wait()
}
小项目3 socks5代理
准备:win需安装nc、curl
nc 127.0.0.1 1080 (cmd下运行),可以直接输入网关+端口去建立tcp连接
go routine 可类比子进程,要比其他语言的子进程开销小很多 可轻松处理上万的并发
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 | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
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)
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, 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) {
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
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)
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本,这里为0x05
// REP Relay field,内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.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
}