在学习了Go语言的基础语法后,我们就要通过实践练习来巩固知识点,在接下来的课程中。老师使用了三个实例来给我们巩固基础:猜谜游戏、在线词典、SOCKS5代理。在课后·我也是亲自动手实践了这些项目,以下是我实践后得出的感悟与思考。
第一个项目是最简单的猜谜游戏,它的要求是:首先通过随机数种子生成随机数,然后用户输入自己所猜测的数字,程序会告知用户猜测的数字与真正数字的大小关系,直到最后猜中,才会返回。
以下是代码:
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Printf("随机生成的数字: %d\n", secretNumber)
fmt.Println("Please input your guess:")
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("You guess is", guess)
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
代码的实现很好理解:首先,使用*reader := bufio.NewReader(os.Stdin)读取输入的结果;然后,用input, err := reader.ReadString('\n')*读取输入的结果;最后,添加循环判断语句,直到结果成功输出。
第二个案例是在线词典,这一部分代码主要由抓包、代码生成和解析response body组成。它使用Go语言发送http请求,实现翻译和词典查询的功能。
以下是代码部分:
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`)
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,ar;q=0.8")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "990faa53baf70befe77f49b01b2daad7")
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", `"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"`)
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/116.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("x-forwarded-for", "1.1.1.1")
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)
}
在这一项目中,我们首先需要将预选的网站发出请求,然后在本地接收,实现在线翻译。
第三个项目是SOCKS5代理的实现。要完成这一项目,首先就要先仔细了解SOCKS5协议的工作原理和运作流程:Socks 协议是一种代理 (Proxy) 协议,它在使用TCP/IP协议的前端机器和服务器机器之间扮演一个中介角色,SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器,模拟了一个前端的行为。这一协议诞生于互联网早期,可以让授权的用户通过单个端口去访问内部的所有资源,同时满足了安全与便捷两大要素。
以下是代码部分:
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const (
socks5Ver = 0x05
cmdBind = 0x01
atypeIPV4 = 0x01
atypeHOST = 0x03
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 connect failed: %v", conn.RemoteAddr(), err)
return
}
}
func auth(reader *bufio.Reader, conn net.Conn) 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)
}
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)
}
_, 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) 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("not supported ver: %v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd: %v", cmd)
}
addr := ""
switch atyp {
case atypeIPV4:
_, 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)
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
}
在以上代码中,我们使用SOCXS5协议实现了一个简单的代理服务,它在监听本地的 1080 端口,连接客户端,然后进行SOCXS5认证,实现目标服务器和客户端的数据联通。
以上便是我在三个课后作业中的实践过程,其过程的艰辛与困难绝非一言可概括的。不过,在实践过程中,我也收获了许多知识,这些都是在课堂上没有完全掌握的,可以说是很有价值的。