第一个项目:猜数字游戏
P1 生成随机数
在1.20版本的Go中,由于已经弃用了rand.Seed函数,因此已经不需要再使用时间戳来初始化随机数种子。可以直接通过下列代码来实现指定范围的随机数生成:
func main(){
maxNum:=100
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
P2 读取用户输入
主要分为三个部分:
- 读取用户输入
- 去除换行符
- 转换输入类型
在处理用户输入数据的时候,需要考虑到不同平台的输入。在Windows系统中,用户输入的每一行末尾通常由两个字符构成:\r (回车符)和 \n (换行符)。这被称为回车换行(CRLF)标记。而在其他操作系统(如Linux和macOS)上,通常只有\n字符表示一行的结束,称为换行(LF)标记。所以,当用户在Windows系统下输入一行文字,按下Enter键后,实际输入的内容是hello\r\n。而在其他系统下输入的内容是hello\n。
最终实现的代码如下:
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n') //这里读取一行输入
if err != nil {
fmt.Println("An error occurred while reading input. Please try again", err)
return
}
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)
P3 实现判断逻辑
采用常规的if else语句即可实现。
P4 实现游戏循环
将总语句放入for循环,屏蔽掉随机数输出,再将判断语句中的return全部改为continue,再在最终判断胜利的时候再进行break即可。
最终实现
源代码如下:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
)
func main() {
/*生成随机数*/
maxNum := 100
//rand.Seed(time.Now().UnixNano())语句中,Seed已被弃用
secretNumber := rand.Intn(maxNum)
//fmt.Println("The secret number is ", 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 occurred while reading input. Please try again", err)
continue
}
input = strings.TrimSuffix(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 it again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try it again")
} else {
fmt.Println("Correct.")
break
}
}
}
第二个项目:在线词典
P1 抓包
本次选用了彩云小译作为词典工具。
抓取方式:
- 打开翻译页面,F12开发者工具
- 打开网络页面,在翻译框随意输入一个单词,等待返回
- 找到
dict项,确定有正确的翻译结果
P2 代码生成
利用curlconverter工具实现请求代码生成。
步骤:
- 找到
dict项后,右键选择“复制-复制为cURL(bash)” - 打开工具,选择Go语言,将代码粘贴到输入框中
- 复制输出结果
P3 生成request body
通过构建结构体来实现。
P4 解析response body
利用JSON转Golang Struct工具实现。复制 P1 中抓取到的dict项的预览json到工具中,根据需要选择相应的转换,再粘贴到总代码中即可。
但是,解析后输出的结果有许多是不需要的,因此我们需要控制相应的输出。
P5 打印结果
从P4中输出的json代码中提取出所需要的音标信息和翻译信息并输出即可。
以下是实现片段:
fmt.Println(word, " UK: ", dictResponse.Dictionary.Prons.En, " US: ", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
P6 完善代码
将整个请求及返回打包成query函数,再在main函数中调用即可。
最终实现
参考官方GitHub项目。
第三个项目:SOCKS5代理
原理
原理图如下图所示。实际建立的是TCP连接。
P0 TCP echo server
建立TCP echo server,可以实现输入什么就输出什么,可以用于测试server的正确性。
这里有个大坑:Windows环境下原生不支持视频中用于测试的nc命令,安装替代用的ncat后会被杀毒软件直接拦截无法运行。还请考虑保证当前网络环境安全的情况下再测试。
P1 auth(认证阶段)
完整的auth函数如下所示:
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)
}
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
}
P2 请求阶段
在本函数中,我们需要完整的读出 ver, cmd, rsv, atyp, dst.addr, dst.port这六个完整的字符串。完整的请求阶段代码如下:
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("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))//这里实现relay阶段的功能
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
}
P3 relay 阶段
实现与真正服务器的tcp连接。
最终测试
代码编写完成后,在Chrome浏览器中需要安装一个SwitchyOmega插件来配置代理。