Go 语言的实战案例 | 豆包MarsCode AI刷题

156 阅读10分钟

三个实例

猜谜游戏 代码导入一些必要的包:

package main

import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
  • fmt:用于输入输出操作,比如 Println
  • math/rand:用于生成随机数。
  • os:提供操作系统功能,例如读取用户输入。
  • strconv:用于字符串和其他基本数据类型(如整数)之间的转换。
  • strings:提供字符串操作函数,例如去掉字符串开头和结尾的空格和换行。
  • time:用于获取当前时间戳,以便生成随机数的种子。

main 函数是程序的入口,在这里实现了所有游戏逻辑。

func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
  1. maxNum := 100:定义游戏的最大值,这里设置为 100,因此随机数会在 0 到 99 之间。
  2. rand.Seed(time.Now().UnixNano()):初始化随机数种子,使用当前时间的纳秒数来生成一个不同的种子,使得每次运行程序生成的随机数不同。 最新的go版本中rand.Seed()已经弃用了,直接rand.Intn()就行
  3. secretNumber := rand.Intn(maxNum):生成一个随机的秘密数字,范围在 0maxNum - 1 之间。

用户输入提示

fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
  • fmt.Println("Please input your guess"):向用户显示提示,要求输入猜测的数字。
  • reader := bufio.NewReader(os.Stdin):创建了一个bufio.Reader对象,用于从标准输入(通常是控制台)读取用户输入

主循环部分,用户会在这个循环中反复输入猜测,直到猜中秘密数字。

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")
  1. input, err := reader.ReadString('\n'):读取用户输入,直到遇到换行符 \n。输入内容会包含换行符,因此我们需要处理。
  2. if err != nil:检查是否发生错误。如果发生错误,就打印错误信息,并 continue 跳过当前循环,要求用户重新输入。
  3. input = strings.Trim(input, "\r\n"):使用 strings.Trim 去掉输入中的回车符和换行符,确保干净的输入以便后续处理。

将输入转换为整数

    guess, err := strconv.Atoi(input)
    if err != nil {
        fmt.Println("Invalid input. Please enter an integer value")
        continue
    }
  • guess, err := strconv.Atoi(input):将用户的输入从字符串转换为整数类型。
  • if err != nil:如果转换失败(即用户输入的不是整数),则打印错误信息“Invalid input. Please enter an integer value”,并跳过当前循环,要求用户重新输入。

比较用户输入和秘密数字

    fmt.Println("Your 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
    }
}

}

  • fmt.Println("Your guess is", guess):输出用户的猜测值。
  • if guess > secretNumber:如果猜测值比秘密数字大,提示“Your guess is bigger than the secret number. Please try again”。
  • else if guess < secretNumber:如果猜测值比秘密数字小,提示“Your guess is smaller than the secret number. Please try again”。
  • else:如果猜测值等于秘密数字,输出“Correct, you Legend!”并 break 跳出循环,游戏结束。

循环、输入输出、字符串转换

命令行词典

向一个翻译或字典 API(彩云小译)发送一个单词查询请求,获取相关的词典解释信息并显示出来。

  1. 包和导入
  • package main:定义一个 Go 程序的入口点,这是所有可执行 Go 程序的标准包名。
  • import 导入了一些必要的标准库:
    • bytes:用于处理字节缓冲区,这里用于将 JSON 转换为字节流。
    • encoding/json:用于 JSON 编码和解码。
    • fmt:格式化 I/O,主要用于打印输出。
    • io/ioutil:用于读取和写入文件或网络数据流。
    • log:用于日志记录,记录错误信息。
    • net/http:用于 HTTP 请求。
    • os:用于处理操作系统功能,这里用来读取命令行参数。
  1. 定义结构体 DictRequestDictResponse
  • DictRequest:这是一个结构体,用来定义请求数据的格式。它包含三个字段:

    • TransType:翻译类型,这里设定为 "en2zh"(表示英文到中文的翻译)。
    • Source:要查询的单词。
    • UserID:用户 ID,这里没有使用。
  • DictResponse:这是一个结构体,用来定义响应数据的格式。它包含一些嵌套结构,用于解析字典和维基百科的响应数据。主要字段如下:

    • Rc:返回码,表示请求状态。
    • Wiki:包含维基百科相关信息。
    • Dictionary:包含词典信息,如发音(Prons)、解释(Explanations)、同义词(Synonym)、反义词(Antonym)等。

定义结构体工具:json-to-go;oktools.net/json2go

  1. 定义 query 函数
func query(word string) {
	client := &http.Client{}
	request := DictRequest{TransType: "en2zh", Source: word}
	buf, err := json.Marshal(request)
	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)
	}
  • client := &http.Client{}:创建一个 HTTP 客户端,用于发送请求。
  • request := DictRequest{TransType: "en2zh", Source: word}:创建一个 DictRequest 实例,将翻译类型设为 "en2zh",并设置要查询的单词。
  • buf, err := json.Marshal(request):将 request 转换为 JSON 格式的字节流。
    • json.Marshal 会将结构体编码为 JSON,如果失败,返回 err
  • var data = bytes.NewReader(buf):将 JSON 字节流转化为一个 Reader,以便后续发送请求时使用。
  • req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data):创建一个 HTTP POST 请求,目标 URL 是彩云小译的词典 API。
  1. 设置请求头信息
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("DNT", "1")
	req.Header.Set("os-version", "")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
	req.Header.Set("app-name", "xy")
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("device-id", "")
	req.Header.Set("os-type", "web")
	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("Sec-Fetch-Site", "cross-site")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
	req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")

这部分代码是设置请求头,用来模仿浏览器请求(从浏览器获取)。这些头部信息告诉服务器这个请求来自一个网页应用,且包含了一些用户设备的信息。 curlconverter.com/#go

  1. 发送请求并处理响应
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode != 200 {
		log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
	}
  • resp, err := client.Do(req):发送请求并获取响应。
  • defer resp.Body.Close():确保响应体被关闭,避免内存泄漏。
  • bodyText, err := ioutil.ReadAll(resp.Body):读取响应体内容,存储在 bodyText 中。
  • if resp.StatusCode != 200:检查 HTTP 状态码,如果不是 200(成功),则输出错误信息。

  1. 解析 JSON 响应并输出结果
	var dictResponse DictResponse
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
	for _, item := range dictResponse.Dictionary.Explanations {
		fmt.Println(item)
	}
  • var dictResponse DictResponse:定义一个 DictResponse 变量,用于存储解析后的响应。
  • err = json.Unmarshal(bodyText, &dictResponse):将 JSON 响应数据解码到 dictResponse 中。
  • fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs):输出查询的单词,以及英式和美式发音。
  • for _, item := range dictResponse.Dictionary.Explanations:遍历 Explanations,输出单词的解释。
  1. 主函数 main
func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
		`)
		os.Exit(1)
	}
	word := os.Args[1]
	query(word)
}
  • if len(os.Args) != 2:检查命令行参数是否正确,确保输入一个要查询的单词。
  • word := os.Args[1]:读取命令行参数,得到用户输入的单词。
  • query(word):调用 query 函数,执行查询操作。

该代码,通过调用彩云小译的 API 来查询单词的解释和发音信息。代码主要流程是构造请求数据,将其转化为 JSON 后发送 HTTP 请求,并解析返回的 JSON 响应,最后在控制台输出结果。 结构体、JSON 编解码、HTTP 请求、错误处理、命令行参数处理、defer等特性

SOCKS5 代理

实现一个简单的SOCKS5代理服务器: 代码结构 主要分为以下几个部分:

  1. 常量声明:定义了一些SOCKS5协议相关的常量。
  2. main 函数:启动TCP服务器,等待客户端连接。
  3. process 函数:处理每一个客户端的请求,包括认证和连接目标服务器。
  4. auth 函数:进行SOCKS5的认证(不进行实际认证,默认通过)。
  5. connect 函数:解析客户端请求,连接目标服务器并建立数据转发。

详细解析:

常量定义:

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
  • socks5Ver: SOCKS5协议的版本号,值为0x05。
  • cmdBind: 连接命令的类型,0x01代表CONNECT请求。
  • atypeIPV4, atypeHOST, atypeIPV6: 地址类型,分别表示IPv4、域名和IPv6。

main 函数

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)
	}
}
  • 监听本地的127.0.0.1:1080端口,等待TCP连接。
  • 每次接受到一个客户端连接后,启动一个新的协程go process(client)来处理客户端请求。

process 函数

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
	}
}
  • 使用defer conn.Close()在函数结束时关闭连接。
  • 调用auth函数进行认证,认证成功后调用connect函数处理连接请求。

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
}
  • 认证过程:读取客户端发送的协议版本(ver)和支持的认证方法数量(methodSize)。
  • 协议版本检查:只接受versocks5Ver(即0x05),否则返回错误。
  • 认证成功后,发送VERMETHOD的响应,表示认证通过。

connect 函数

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))
	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请求头。
    • ver:协议版本,检查是否为0x05。
    • cmd:命令类型,只处理cmdBind(CONNECT)请求。
    • atyp:目标地址类型,可能是IPv4、域名或IPv6。
  • 解析地址和端口
    • 如果atypatypeIPV4,读取4字节的IPv4地址。
    • 如果atypatypeHOST,读取变长域名地址。
  • 建立到目标服务器的连接:通过net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))连接目标地址和端口。
  • 双向数据转发:启动两个协程,将客户端和目标服务器的流量进行转发。
    • io.Copy(dest, reader):从客户端读取数据转发到目标服务器。
    • io.Copy(conn, dest):从目标服务器读取数据转发到客户端。

此代码实现了一个简单的SOCKS5代理服务器,处理客户端的认证请求并将流量转发到指定的目标服务器。

SOCKS5代理的典型应用包括:

  1. 隐匿身份:当你想隐匿自己在网上的身份时,可以通过代理服务器访问目标网站,这样目标网站看到的“来源”是代理服务器,而不是你真实的IP地址。
  2. 访问受限资源:在某些情况下,某些网站可能对直接访问有限制,通过SOCKS5代理可以帮助绕过这些限制。
  3. 数据加速:在一些公司网络或有特定用途的网络中,通过代理服务器可以优化和加速数据传输。