三个实例
猜谜游戏 代码导入一些必要的包:
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)
maxNum := 100:定义游戏的最大值,这里设置为 100,因此随机数会在 0 到 99 之间。rand.Seed(time.Now().UnixNano()):初始化随机数种子,使用当前时间的纳秒数来生成一个不同的种子,使得每次运行程序生成的随机数不同。 最新的go版本中rand.Seed()已经弃用了,直接rand.Intn()就行secretNumber := rand.Intn(maxNum):生成一个随机的秘密数字,范围在0到maxNum - 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")
input, err := reader.ReadString('\n'):读取用户输入,直到遇到换行符\n。输入内容会包含换行符,因此我们需要处理。if err != nil:检查是否发生错误。如果发生错误,就打印错误信息,并continue跳过当前循环,要求用户重新输入。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(彩云小译)发送一个单词查询请求,获取相关的词典解释信息并显示出来。
- 包和导入
package main:定义一个 Go 程序的入口点,这是所有可执行 Go 程序的标准包名。import导入了一些必要的标准库:bytes:用于处理字节缓冲区,这里用于将 JSON 转换为字节流。encoding/json:用于 JSON 编码和解码。fmt:格式化 I/O,主要用于打印输出。io/ioutil:用于读取和写入文件或网络数据流。log:用于日志记录,记录错误信息。net/http:用于 HTTP 请求。os:用于处理操作系统功能,这里用来读取命令行参数。
- 定义结构体
DictRequest和DictResponse
-
DictRequest:这是一个结构体,用来定义请求数据的格式。它包含三个字段:TransType:翻译类型,这里设定为"en2zh"(表示英文到中文的翻译)。Source:要查询的单词。UserID:用户 ID,这里没有使用。
-
DictResponse:这是一个结构体,用来定义响应数据的格式。它包含一些嵌套结构,用于解析字典和维基百科的响应数据。主要字段如下:Rc:返回码,表示请求状态。Wiki:包含维基百科相关信息。Dictionary:包含词典信息,如发音(Prons)、解释(Explanations)、同义词(Synonym)、反义词(Antonym)等。
定义结构体工具:json-to-go;oktools.net/json2go
- 定义
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。
- 设置请求头信息
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
- 发送请求并处理响应
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(成功),则输出错误信息。
- 解析 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,输出单词的解释。
- 主函数
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代理服务器: 代码结构 主要分为以下几个部分:
- 常量声明:定义了一些SOCKS5协议相关的常量。
main函数:启动TCP服务器,等待客户端连接。process函数:处理每一个客户端的请求,包括认证和连接目标服务器。auth函数:进行SOCKS5的认证(不进行实际认证,默认通过)。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)。 - 协议版本检查:只接受
ver为socks5Ver(即0x05),否则返回错误。 - 认证成功后,发送
VER和METHOD的响应,表示认证通过。
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。
- 解析地址和端口:
- 如果
atyp是atypeIPV4,读取4字节的IPv4地址。 - 如果
atyp是atypeHOST,读取变长域名地址。
- 如果
- 建立到目标服务器的连接:通过
net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))连接目标地址和端口。 - 双向数据转发:启动两个协程,将客户端和目标服务器的流量进行转发。
io.Copy(dest, reader):从客户端读取数据转发到目标服务器。io.Copy(conn, dest):从目标服务器读取数据转发到客户端。
此代码实现了一个简单的SOCKS5代理服务器,处理客户端的认证请求并将流量转发到指定的目标服务器。
SOCKS5代理的典型应用包括:
- 隐匿身份:当你想隐匿自己在网上的身份时,可以通过代理服务器访问目标网站,这样目标网站看到的“来源”是代理服务器,而不是你真实的IP地址。
- 访问受限资源:在某些情况下,某些网站可能对直接访问有限制,通过SOCKS5代理可以帮助绕过这些限制。
- 数据加速:在一些公司网络或有特定用途的网络中,通过代理服务器可以优化和加速数据传输。