Go 语言的实战案例
猜谜游戏
版本1
package main
import (
"fmt"
"math/rand"
)
func main() {
maxNum := 100
secretNumber := rand.Intn(maxNum) //rand.Intn() 函数返回 0-N 之间的随机数。这里,N 是函数 rand.Intn() 中指定的数字。
fmt.Println("The secret number is ", secretNumber)
}
rand.Intn() 函数返回 0-N 之间的随机数。这里,N 是函数 rand.Intn() 中指定的数字。
版本2
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano()) //设置随机数种子
secreatNum := rand.Intn(maxNum)
fmt.Println("The secreat number is ", secreatNum)
}
rand设置随机种子是全局性的。如果不初始化种子的默认随机源,用 rand 库是协程安全的。
在多协程情况下,应该重新新建个rand,参考grpc-go的做法:
package grpcrand
import (
"math/rand"
"sync"
"time"
)
var (
r = rand.New(rand.NewSource(time.Now().UnixNano()))
mu sync.Mutex
)
// Int63n implements rand.Int63n on the grpcrand global source.
func Int63n(n int64) int64 {
mu.Lock()
res := r.Int63n(n)
mu.Unlock()
return res
}
// Intn implements rand.Intn on the grpcrand global source.
func Intn(n int) int {
mu.Lock()
res := r.Intn(n)
mu.Unlock()
return res
}
// Float64 implements rand.Float64 on the grpcrand global source.
func Float64() float64 {
mu.Lock()
res := r.Float64()
mu.Unlock()
return res
}
版本3
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
//maxNum := 100
rand.Seed(time.Now().UnixNano())
//secreatNum := rand.Intn(maxNum)
//读取io
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
//windows的换行是\r\n,unix的是\n
input = strings.TrimSuffix(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
fmt.Println("You guess is ", guess)
}
这里需要注意的地方就是go语言不允许未使用的变量,这跟其它语言有点区别。
在windows平台上,换行符是\r\n,而linux是\n,视频使用\n,应该代码是在linux上运行。
版本4
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
// 与大多数程序语言不同,Go不允许未使用的变量。这是因为设计决策在此级别上强制执行,而不是像其他语言那样作为每个开发人员的可选选择。
// 因此,您可以使用空白标识符作为提到的另一个答案,但这是因为Go的工作方式
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secreatNum := rand.Intn(maxNum)
fmt.Println("Please input your guess")
for {
//读取io
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
//windows的换行是\r\n,unix的是\n
input = strings.TrimSuffix(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
if guess > secreatNum {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secreatNum {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct,you Legend!")
break
}
}
}
爬虫实战
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki Wiki `json:"wiki"`
Dictionary Dictionary `json:"dictionary"`
}
type Wiki struct {
}
type Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
}
type Dictionary struct {
Prons Prons `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []string `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage:simpleDict Word example:simpleDict hello`) //这里字符串不是用'而是要用`
os.Exit(1)
}
word := os.Args[1]
client := &http.Client{}
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewBuffer(buf)
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,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "687688507465ec9586040967ca42e24b")
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=114, Microsoft")
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/114.0.0.0 Safari/537.36 Edg/114.0.0.0")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// fmt.Printf("%s\n", bodyText)
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
// fmt.Printf("%#v\n", dictResponse)
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
注意点: 这里字符串不是用'而是要用`
fmt.Fprintf(os.Stderr, `usage:simpleDict Word example:simpleDict hello`)
浏览器复制出来的curl命令需要将^替换成\,才可以使用curl command to Go工具进行转换
Socks5代理
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
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)
// for {
// b, err := reader.ReadByte()
// if err != nil {
// break
// }
// _, err = conn.Write([]byte{b})
// if err != nil {
// break
// }
// }
// }
// 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
// }
// log.Println("auth success")
// }
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
}
// log.Println("auth success")
err = connect(reader, conn)
if err != nil {
log.Printf("client %v connect failed:%v", conn.RemoteAddr(), err)
return
}
}
const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeTPV6 = 0x04
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)
}
mehodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read methodSize failed:%w", err)
}
method := make([]byte, mehodSize)
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
log.Println("ver", ver, "method", method)
_, 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("read header failed:%w", err)
}
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 atypeTPV6:
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
}
![图片转存失败,建议将图片保存下来直接上传
socks5连接过程:
首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、 relay 阶段。
第一个握手阶段,浏览器会向 socks5 代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5 服务器会选中一个认证方式,返回给浏览器。如果返回的是 00 的话就代表不需要认证,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。
第三个阶段是请求阶段,认证通过之后浏览器会 socks5 服务器发起请求。主要信息包括 版本号,请求的类型,一般主要是 connection 请求,就代表代理服务器要和某个域名或者某个 IP 地址某个端口建立 TCP 连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。
第四个阶段是 relay 阶段。此时浏览器会发送 正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上 代理服务器并不关心流量的细节,可以是 HTTP流量,也可以是其它 TCP 流量。 这个就是 socks5 协议的工作原理
协商认证报文格式(以字节为单位)::
| VER | NMETHODS | METHODS |
|---|---|---|
| 1 | 1 | 1到255 |
VER是SOCKS版本,这里应该是0x05;
NMETHODS是METHODS部分的长度;
METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
- 0x00 不需要认证
- 0x01 GSSAPI
- 0x02 用户名、密码认证
- 0x03 – 0x7F由IANA分配(保留)
- 0x80 – 0xFE为私人方法保留
- 0xFF 无可接受的方法
浏览器的请求报文格式:
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|---|---|---|---|---|---|
| 1 | 1 | X'00’ | 1 | Variable | 2 |
CMD是SOCK的命令码:
- 0x01表示CONNECT请求
- 0x02表示BIND请求
- 0x03表示UDP转发
RSV 默认 0x00,保留
ATYP 后面的地址类型:
- 0x01 IPv4地址,DST ADDR部分4字节长度
- 0x03 域名,DST ADDR部分第一个字节为域名长度,DST ADDR剩余的内容为域名,没有\0结尾。
- 0x04 IPv6地址,16个字节长度。
DST.ADDR 目的地址
DST.PROT 目的端口
注意:如果为UDP 这里的目的地址与端口为 客户端的UDP信息。告诉代理服务器以后会以这个地址进行UDP信息交互。
服务器对请求报文进行确认的格式:
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|---|---|---|---|---|---|
| 1 | 1 | X'00’ | 1 | Variable | 2 |
REP 应答字段
- 0x00 表示成功
- 0x01 普通SOCKS服务器连接失败
- 0x02 现有规则不允许连接
- 0x03 网络不可达
- 0x04 主机不可达
- 0x05 连接被拒
- 0x06 TTL超时
- 0x07 不支持的命令
- 0x08 不支持的地址类型
- 0x09 0xFF未定义
BND.ADDR 服务器绑定的地址
BND.PORT 以网络字节顺序表示的服务器绑定的端口
上述流程完成后,将会进行数据转发的过程。
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
注意:
使用curl发送请求命令如下,这里添加了参数-4,意思是用ipv4地址:
curl --socks5 127.0.0.1:1080 -4 -v <http://www.qq.com>