一、猜谜游戏
这是一个简单的猜谜游戏程序,玩家需要猜出系统随机生成的秘密数字。如果玩家的猜测大于秘密数字(0-100),则系统会提示"Your guess is bigger than the secret number. Please try again";如果猜测小于秘密数字,则系统会提示"Your guess is smaller than the secret number. Please try again";当猜测等于秘密数字时,系统会输出"Correct, you Legend!",游戏结束。
这个程序包含以下主要部分:
- 导入所需的包,包括"bufio"、"fmt"、"math/rand"和"os"。
- 定义最大数字
maxNum,在这个例子中为100。 - 使用
rand.Intn()函数生成一个0到maxNum-1之间的随机整数作为秘密数字secretNumber。 - 进入游戏循环,不断接收玩家的输入并进行处理。
- 使用
bufio.NewReader()和os.Stdin创建一个输入读取器reader。 - 在游戏循环中,使用
reader.ReadString('\n')读取玩家输入的内容,直到遇到换行符。 - 使用
strings.Trim(input, "\r\n")去除输入中的回车符和换行符,得到处理后的输入input。 - 使用
strconv.Atoi(input)将处理后的输入转换为整数类型guess。 - 根据玩家的猜测与秘密数字之间的关系,输出相应的提示信息。
- 如果玩家猜中了秘密数字,输出"Correct, you Legend!",游戏结束。
- 代码实现
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func generateSecretNumber(maxNum int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(maxNum)
}
func getUserInput() int {
fmt.Println("Please input your guess:")
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value.")
return getUserInput()
}
return guess
}
func main() {
maxNum := 100
secretNumber := generateSecretNumber(maxNum)
fmt.Println("Guess the secret number between 0 and", maxNum-1)
for {
guess := getUserInput()
fmt.Println("You guessed:", 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
}
}
}
二、在线词典
在线词典要求用户输入要翻译的内容。用户输入完内容后,程序将调用 query(word) 函数来进行翻译,并将翻译结果输出到控制台。
在程序的 main() 函数中,我们首先创建一个 scanner 对象,它是 bufio 包提供的一个方便读取输入的工具。然后,程序进入一个无限循环,不断等待用户的输入。
每次循环开始时,程序会输出一条提示信息:"请输入要翻译的内容:"。然后调用 scanner.Scan() 来读取用户输入的内容。scanner.Scan() 会等待用户输入,并将输入的内容读取为字符串。用户输入的内容被保存在变量 word 中。
接下来,程序将调用 query(word) 函数,把用户输入的内容作为参数传递给它。query(word) 函数是我们自己定义的,它用于发送翻译请求,并解析返回的翻译结果。
在 query(word string) 函数中,我们首先创建一个 HTTP 客户端 client,用于发送 HTTP 请求。然后,我们定义了一个 DictRequest 结构体,用于存储请求的数据。我们填充这个结构体的字段,包括 TransType(翻译类型)和 Source(要翻译的内容)。
接着,我们将 DictRequest 结构体转换成 JSON 格式,并将其作为请求体发送给在线词典的 API。我们设置了请求头的一些信息,如 Content-Type 为 application/json;charset=UTF-8。
然后,我们使用 client.Do(req) 发送 POST 请求,并获取返回的响应对象 resp。我们从响应对象中读取返回的 JSON 数据,然后使用 json.Unmarshal() 解析成我们定义的 DictResponse 结构体,以便获取翻译结果。
最后,我们将翻译结果输出到控制台。这里输出了 "UK:" 和 "US:" 后跟着英式和美式发音,以及 dictResponse.Dictionary.Explanations 中的解释内容。
程序会一直循环等待用户输入,直到用户主动退出。如果用户输入的内容不符合预期,比如不是有效的整数,或者在线词典API返回了错误信息,程序会输出相应的错误提示,并重新等待用户输入。
总结:这个程序实现了一个简单的在线词典翻译功能。用户可以通过命令行输入要翻译的内容,然后程序会将其发送给在线词典 API 进行翻译,并将翻译结果输出到控制台。通过这个例子,我们可以学习如何使用 Go 的标准库来进行 HTTP 请求和 JSON 数据的处理,同时也了解了如何处理用户输入和错误情况。
- 代码实现
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `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"`
}
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)
}
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")
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))
}
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)
}
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Println("请输入要翻译的内容:")
scanner.Scan()
word := scanner.Text()
query(word)
}
}
三、SOCKS服务器
这是一个简单的 Socks5 代理服务器的实现,它可以将客户端的网络请求转发到目标服务器。
- 首先,在
main函数中,我们创建一个监听器(listener)并绑定到本地的127.0.0.1:1080地址上。这样,代理服务器就会在本地监听1080端口,等待客户端的连接请求。 process函数用来处理每个客户端连接。当一个客户端连接上来后,我们先进行 Socks5 协议的认证过程,即客户端发送认证请求,服务器返回认证响应。在这个实现中,我们只支持无需认证(0x00)的方式。- 接下来,客户端会发送连接请求,包括请求的目标地址和端口。服务器收到连接请求后,会解析请求,提取出目标地址和端口。然后,代理服务器会建立与目标服务器的连接,并将客户端的请求转发给目标服务器。
- 在建立与目标服务器的连接后,代理服务器会使用两个 goroutine 来进行数据的传输。一个 goroutine 负责将客户端发送的数据转发给目标服务器,另一个 goroutine 负责将目标服务器返回的数据转发给客户端。使用两个 goroutine 进行并发的数据传输,可以实现同时处理多个客户端连接。
- 当任意一个 goroutine 发生错误或数据传输完成时,它会取消另一个 goroutine 的执行,并关闭连接,释放资源。
总体来说,这个代码实现了一个简单的 Socks5 代理服务器,可以接受客户端连接,并将客户端的网络请求转发到目标服务器。这个代理服务器目前只支持无需认证的方式,并且只支持 IPv4 地址类型,对于 IPv6 地址类型尚未实现。在实际应用中,还可以进一步扩展功能,例如添加用户认证、支持更多的地址类型等。
- 代码实现
package main
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
}
这段代码实现了一个简单的 SOCKS5 代理服务器。它监听在本地的 1080 端口,并接受来自客户端的连接。对每个连接,它会尝试进行 SOCKS5 认证,然后根据客户端请求连接到目标服务器,并将客户端与目标服务器之间的数据进行转发。
现在,我来逐步解释代码的主要部分:
- 首先,定义了常量
socks5Ver、cmdBind、atypeIPV4和atypeHOST,这些常量分别表示 SOCKS5 协议版本号、连接请求命令、目标地址类型(IPv4 和 域名)等。 main()函数启动了 SOCKS5 代理服务器,监听在 127.0.0.1:1080 端口上。在主循环中,它接受客户端的连接,并为每个客户端连接启动一个 goroutine 处理。process()函数是每个客户端连接的处理函数,它首先调用auth()函数进行 SOCKS5 认证,然后调用connect()函数连接到目标服务器,并实现数据转发。auth()函数用于进行 SOCKS5 认证。它从客户端读取协议版本号和支持的认证方法数量,并向客户端返回认证成功的响应。connect()函数用于处理客户端的连接请求,并连接到目标服务器。它首先解析客户端的请求,获取目标地址和端口。然后,它连接到目标服务器,并通过 goroutine 进行数据转发。- 在进行数据转发时,使用了
context.Context来管理 goroutine。每个连接都创建了一个上下文ctx和一个cancel函数。当客户端或目标服务器的连接关闭时,调用cancel()函数通知其他 goroutine 结束转发,并返回错误。
测试
- 启动程序
- 打开命令行窗口输入
curl --socks5 127.0.0.1 1080 -v http://www.baidu.com以测试