一、猜数字游戏
要求游戏开始时生成一个随机数,每轮由玩家输入一个数字,之后程序反馈是大于答案还是小于答案,知道猜对结果游戏结束。
主要是 math/rand 库的应用,以及熟悉一下标准输入输出。
按照题目,首先我们生成一个随机结果,注意随机数种子的设置。
rand.NewSource(time.Now().UnixNano())
answer := rand.Intn(100)
每轮游戏反馈结果:
if input > answer {
fmt.Print("Your answer is too high, please try again: ")
_, _ = fmt.Scan(&input)
} else {
fmt.Print("Your answer is too low, please try again: ")
_, _ = fmt.Scan(&input)
}
显然游戏不会只进行一轮,需要循环直到猜对答案,完整代码如下:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.NewSource(time.Now().UnixNano())
answer := rand.Intn(100)
var input int
fmt.Print("Enter a number: ")
_, _ = fmt.Scan(&input)
for input != answer {
if input > answer {
fmt.Print("Your answer is too high, please try again: ")
_, _ = fmt.Scan(&input)
} else {
fmt.Print("Your answer is too low, please try again: ")
_, _ = fmt.Scan(&input)
}
}
fmt.Println("You are right! The answer is ", answer)
}
二、命令行字典
要求模拟请求彩云小译的api实现一个命令行字典程序。
主要是对 net/http 和 encoding/json 库的使用,以及结构体embedding,标签之类的技巧的练习。
我们进入 fanyi.caiyunapp.com/ 后 f12 -> NetWork,然后输入任意内容等待翻译,可以得到翻译的 api 为https://api.interpreter.caiyunai.com/v1/dict。
以及请求方式为POST,请求json格式和返回json格式如下:
分析一下返回json,不难发现其中返回格式里我们需要的信息为: entry, explanations, prons, wqx_example。
所以我们定义结构体如下(只需要设置需要的字段即可):
type Req struct {
Source string `json:"source"`
TransType string `json:"trans_type"`
}
func NewReq(source, transType string) *Req {
return &Req{
Source: source,
TransType: transType,
}
}
type Dictionary struct {
Entry string `json:"entry"`
Explanations []string `json:"explanations"`
Prons map[string]string `json:"prons"`
WqxExample [][2]string `json:"wqx_example"`
}
type Resp struct {
Dictionary `json:"dictionary"`
}
然后模拟请求解析json即可,注意设置header信息,需要设置X-Authorization 字段,否则请求不成功。
func Request(req *Req) (*Resp, error) {
bytesData, err := json.Marshal(*req)
if err != nil {
return nil, err
}
request, err := http.NewRequest(Method, DictApi, bytes.NewBuffer(bytesData))
if err != nil {
return nil, err
}
request.Header.Set("User-Agent", UserAgent)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("X-Authorization", Authorization)
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(response.Body)
if response.StatusCode != 200 {
return nil, errors.New(response.Status)
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
var resp Resp
err = json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
最后处理一下命令行参数和结果输出,完整代码如下:
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
)
const (
DictApi = "https://api.interpreter.caiyunai.com/v1/dict"
Method = "POST"
UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
Authorization = "token:qgemv4jr1y38jyq6vhvi"
)
type Req struct {
Source string `json:"source"`
TransType string `json:"trans_type"`
}
func NewReq(source, transType string) *Req {
return &Req{
Source: source,
TransType: transType,
}
}
type Dictionary struct {
Entry string `json:"entry"`
Explanations []string `json:"explanations"`
Prons map[string]string `json:"prons"`
WqxExample [][2]string `json:"wqx_example"`
}
type Resp struct {
Dictionary `json:"dictionary"`
}
func Request(req *Req) (*Resp, error) {
bytesData, err := json.Marshal(*req)
if err != nil {
return nil, err
}
request, err := http.NewRequest(Method, DictApi, bytes.NewBuffer(bytesData))
if err != nil {
return nil, err
}
request.Header.Set("User-Agent", UserAgent)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("X-Authorization", Authorization)
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(response.Body)
if response.StatusCode != 200 {
return nil, errors.New(response.Status)
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
var resp Resp
err = json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
func main() {
args := os.Args[1:]
if len(args) != 2 {
fmt.Println("Usage: go run main.go <source> <trans_type>")
return
}
req := NewReq(args[0], args[1])
resp, err := Request(req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.Dictionary.Entry)
for _, explanation := range resp.Dictionary.Explanations {
fmt.Println(explanation)
}
for k, v := range resp.Dictionary.Prons {
fmt.Println(k, ":", v)
}
for _, wqxExample := range resp.Dictionary.WqxExample {
fmt.Println(wqxExample[0], wqxExample[1])
}
}
三、Socks5 代理
大概按照如下流程实现即可。
- 建立连接: 客户端连接到代理服务器的端口(1080)。
- 认证阶段: 客户端发送 SOCKS5 版本和支持的认证方法。 代理服务器告诉客户端不需要认证。
- 请求阶段: 客户端发送请求,包含目标地址和端口。 服务器解析请求,获取目标服务器的信息。
- 连接目标服务器: 代理服务器与目标服务器建立连接。
- 响应客户端: 服务器通知客户端连接成功,准备传输数据。
- 数据转发: 数据在客户端和目标服务器之间来回传输,代理服务器负责中转。
完整代码:
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const (
socks5Ver = 0x05
cmdBind = 0x01
atypeIPV4 = 0x01
atypeHOST = 0x03
atypeIPV6 = 0x04
)
func process(conn net.Conn) {
defer func(conn net.Conn) {
err := conn.Close()
if err != nil {
return
}
}(conn)
reader := bufio.NewReader(conn)
err := auth(reader, conn)
if err != nil {
log.Printf("client %v auth error: %v", conn.RemoteAddr(), err)
return
}
log.Printf("client %v auth success", conn.RemoteAddr())
err = connect(reader, conn)
if err != nil {
log.Printf("client %v connect error: %v", conn.RemoteAddr(), err)
return
}
}
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver: %w", err)
}
if ver != socks5Ver {
return fmt.Errorf("not supported socks5 ver: %v", ver)
}
methodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read method size: %w", err)
}
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method: %w", err)
}
log.Println("ver", ver, "method", method)
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write socks5 ver: %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: %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 atypeIPV4:
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read ityp: %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 host size: %w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host: %w", err)
}
addr = string(host)
case atypeIPV6:
return errors.New("not supported yet")
default:
return fmt.Errorf("invalid atyp: %v", atyp)
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port: %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 dest: %w", err)
}
defer func(dest net.Conn) {
err := dest.Close()
if err != nil {
return
}
}(dest)
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
}
func main() {
server, err := net.Listen("tcp", ":1080")
if err != nil {
panic(err)
}
defer func(server net.Listener) {
err := server.Close()
if err != nil {
return
}
}(server)
for {
client, err := server.Accept()
if err != nil {
log.Printf("accept error: %v", err)
continue
}
go process(client)
}
}
- 常量定义:
- SOCKS5 版本和命令类型等常量。
- process 函数:
- 处理每个连接。
- 调用 auth 进行认证。
- 调用 connect 处理连接请求。
- auth 函数:
- 验证 SOCKS5 版本。
- 读取并忽略客户端支持的认证方法。
- 返回成功响应(不需要认证)。
- connect 函数:
- 读取请求的版本、命令、地址类型。
- 根据地址类型解析目标地址。
- 读取端口号。
- 建立到目标服务器的连接。
- 返回成功响应给客户端。
- 开启两个协程进行数据转发。
- main 函数:
- 监听 1080 端口。
- 接受客户端连接并调用 process。