通过三个练习(猜数字、在线词典、SOCK5)从简单到复杂,借助一些网站工具,达到效果
猜数字
需求与功能分析
各功能实现
- 获取输入与返回结果
import 包 fmt
输入 fmt.Scanln(&num)
输出fmt.Println("你猜的数是:", num)
- 生成随机数
import 包 math/rand time
通过时间生成随机数种子 rand.Seed(time.Now().Unix())
产生随机数 ran := rand.Intn(100)
- 循环判断是否继续游戏
增加一个for死循环,由用户输入true/false判断是否继续
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var flag bool = true
var num int
rand.Seed(time.Now().Unix())
ran := rand.Intn(100)
for flag == true {
fmt.Printf("输入猜测的数(0-100)")
fmt.Scanln(&num)
if ran == num {
fmt.Println("你猜对了")
flag = false
} else {
fmt.Println("你猜错了,数值为", ran)
fmt.Println("你要继续吗?")
fmt.Scanln(&flag)
}
}
}
展示效果
在线词典
需求与功能分析
各功能实现(两种翻译引擎)
- 获取输入
import 包 fmt
输入 fmt.Scanln(&word)
- 定义请求/相应结构体
请求结构体(右键负载复制object,粘贴至oktools.net/json2go 转为结构体)
// 请求结构体
type Myreq struct {
Trans_type string `json:"trans_type"`
Source string `json:"source"`
}
响应结构体(右键预览复制object,粘贴至oktools.net/json2go 转为结构体)
// 响应结构体
type AutoGenerated struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `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"`
} `json:"dictionary"`
}
- 通信实现(右击请求响应的curl,复制,输入curlconverter.com/go/,获取为go通信代码,根据上述结构体构造逻辑)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
func main() {
//获取输入
var word string
fmt.Scanln(&word)
query1(word)
}
func query1(word string) {
//创建请求内容
client := &http.Client{}
request := Myreq{Trans_type: "en2zh", Source: word}
buf, err := json.Marshal(request) //结构体转成json
if err != nil {
log.Fatal(err)
}
//创建请求
var data = bytes.NewReader(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-US;q=0.8,en;q=0.7,en-GB;q=0.6")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "f46430288c84d1b75ef71587166e43c6")
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="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
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/115.0.0.0 Safari/537.36 Edg/115.0.1901.183")
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 myreps AutoGenerated
err = json.Unmarshal(bodyText, &myreps)
if err != nil {
log.Fatal(err)
}
fmt.Println(word, " UK:", myreps.Dictionary.Prons.En, " US:", myreps.Dictionary.Prons.EnUs)
for _, item := range myreps.Dictionary.Explanations {
fmt.Println(item)
}
}
- 测试效果
课后作业(2、3)
增加另一种引擎
- 获取结构体——海词翻译
请求只有一个字符串很简单
响应结构体(右键预览复制object,粘贴至oktools.net/json2go 转为结构体)
type HaiciResponse struct {
Ok int `json:"ok"`
Out struct {
Query string `json:"query"`
Dict string `json:"dict"`
Transform []interface{} `json:"transform"`
Translation [][2]string `json:"translation"`
} `json:"out"`
}
- 获取通信代码——海词翻译
func query2(word string) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
url := "http://fanyi.dict.cn/search.php?jsoncallback=jQuery19105906961361475107_1692691662426&q=" + word + "&_=1692691662447"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "application/json;charset=UTF-8, text/plain, */*; q=0.01")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Cookie", "__utma=7761447.1192487628.1690789693.1690793682.1692691663.3; __utmc=7761447; __utmz=7761447.1692691663.3.3.utmcsr=cn.bing.com|utmccn=(referral)|utmcmd=referral|utmcct=/; __utmt=1; __utmb=7761447.1.10.1692691663; __gads=ID=29c666bb1b061390-226f8bab1de300e9:T=1690789691:RT=1692691662:S=ALNI_MYMAF1XRCTm_juD75qHxvKqKAH3ig; __gpi=UID=00000c259d0d4c55:T=1690789691:RT=1692691662:S=ALNI_MZyUt2PZTBbyZ8pAxeTsuupuXiTuQ")
req.Header.Set("Referer", "http://fanyi.dict.cn/")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
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)
}
first := strings.Index(string(bodyText), "(")
last := strings.LastIndex(string(bodyText), ")")
responseBody := bodyText[first+1 : last]
//转对象
var Myreps HaiciResponse
err = json.Unmarshal(responseBody, &Myreps)
if err != nil {
log.Fatal(err)
}
fmt.Println(word)
for _, sentence := range Myreps.Out.Translation {
fmt.Printf(sentence[0] + " ")
fmt.Println(sentence[1])
}
}
- 测试效果
并行请求两个翻译引擎
- 并行采用goroutine
func go2(word string) {
go func(word string) {
query1(word)
}(word)
go func(word string) {
query2(word)
}(word)
time.Sleep(time.Second)
}
- 结果
SOCK5代理
大致流程图
- 导包
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
- 定义代理所需的常量
const (
socks5Ver = 0x05
cmdBind = 0x01 //connect
atypeIPV4 = 0x01
atypeHOST = 0x03 //域名
atypeIPV6 = 0x04
)
- 实际调用过程
net.Listen("tcp", "127.0.0.1:1080")
——tcp连接 监听IP地址为:127.0.0.1的1080端口server.Accept()
——for循环中等待连接(一次一个连接)go process(client)
——for循环中每次获取到连接,就开启一个协程进入通信代理
func main() {
//tcp连接 监听IP地址为:127.0.0.1的1080端口
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)
}
}
- 代理通信详解
auth(reader, conn)
——调用代理函数()connect(reader, conn)
——(代理成功后)调用连接
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
}
}
- 代理(auth)详情
- 按顺序逐字节读取(ver,methodSize,方法s)
ver, err := reader.ReadByte()
——读取;if ver != socks5Ver
——校验版本methodSize, err := reader.ReadByte()
——读取;method := make([]byte, methodSize)
——按照methodSize的大小创建等大小的byte[]_, err = io.ReadFull(reader, method)
——将剩下的方法读到method缓冲区_, err = conn.Write([]byte{socks5Ver, 0x00})
——把socks版本号写回conn,0x00 表示认证成功
- 按顺序逐字节读取(ver,methodSize,方法s)
/*
ver——协议版本,socks5为0x05
methodSize——支持认证的方法数量
method——对应nmethods的值为多少,就有多少字节
*/
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
//按顺序逐字节读取(ver,methodSize,方法s)
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
//检验socks版本号
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)
}
//按照methodSize的大小创建等大小的byte[]
method := make([]byte, methodSize)
//将剩下的方法读到method缓冲区
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
//log.Println("ver", ver, "method", method)
//ver method
//把socks版本号写回conn,0x00 表示认证成功
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
- 连接详情
buf := make([]byte, 4)
——创建一个大小为4的byte[],用来读取header_, err = io.ReadFull(reader, buf)
——tips:经过auth,reader目前读取指针指向header,读取headerver, cmd, atype := buf[0], buf[1], buf[3]
——header 的第1,2,4字节分别是版本号ver、命令码cmd(0x01表示connect请求,0x02表示bind...)、目标地址类型atype(有三种可能)if ver != socks5Ver
——验证socks版本号if cmd != cmdBind
——验证命令码switch atype
——通过确认目标地址类型获取地址_, err = io.ReadFull(reader, buf[:2])
——接着读取端口,两个字节
- relay阶段
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
——tcp连接指定地址和端口defer dest.Close()
——延迟关闭连接_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
——server确定能够代理,返回响应ctx, cancel := context.WithCancel(context.Background())
——利用根context获得父context和取消函数_, _ = io.Copy(dest, reader)
——并发执行代理转发<-ctx.Done()
——取消并行事件
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
//创建一个大小为4的byte[],用来读取header
buf := make([]byte, 4)
//tips:经过auth,reader目前读取指针指向header(已读ver,methodSize,method)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
/*header 的第1,2,4字节分别是版本号ver、
命令码cmd(0x01表示connect请求,0x02表示bind...)、
目标地址类型atype(有三种可能)*/
ver, cmd, atype := buf[0], buf[1], buf[3]
//验证socks版本号
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
//验证命令码
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
//通过确认目标地址类型获取地址
addr := ""
switch atype {
case atypeIPV4:
//继续接着读取reader(此时内容为目的地址)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atpye 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 atpye failed:%w", err)
}
addr = string(host)
case atypeIPV6:
//返回错误,不支持ipv6
return errors.New("IPV6: no supported yet")
default:
return errors.New("invalid atype")
}
//接着读取端口,两个字节
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
//二进制转大端字节序的 16 位无符号整数
port := binary.BigEndian.Uint16(buf[:2])
//relay阶段
//tcp连接指定地址和端口
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)
//server确定能够代理,返回响应
//ver methodSize method cmd atype 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)
}
//利用根context获得父context和取消函数
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
}