猜谜游戏
-
随机数生成
-
使用
bufio.NewReader读取用户输入,输入以Enter键结束 -
input, err := reader.ReadString('\n')从reader中读取输入,直到遇到\n换行符为止。并返回读取的字符串以及可能的错误。 -
input = strings.TrimSuffix(input, "\n")需要修改为input = strings.TrimSuffix(input, "\r\n")因为在Windows中,行结束符可以是\r\n也可以是\n,而strconv会将其视为输入的一部分; 而在Linux/Mac中,行结束符只能是\n,strconv不会将其视为输入的一部分
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
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 occured while reading input. Please try again", err)
continue
}
input = strings.TrimSuffix(input, "\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
}
}
}
知识点总结:
1. 随机数的生成
2. 控制台输入的读取与处理
3. 循环以及if的使用
在线词典
前期准备
-
JSON结构代码自动生成:JSON转Golang Struct - 在线工具 - OKTools
请求创建
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
请求头设置
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
.........
请求体的实现
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
当我们使用encoding/json将这个结构体序列化为JSON字符串时,会使用json:"字段名"定义的标签作为JSON对象的键:
{
"trans_type": "en2zh",
"source": "hello",
"user_id": ""
}
然后我们可以将这个JSON字符串作为HTTP请求的body,发送给API。
发起请求
resp, err := client.Do(req)
读取响应
响应结构定义直接利用JSON结构代码自动生成
//响应体结构
type DictResponse struct {
}
bodyText, err := ioutil.ReadAll(resp.Body)
反序列化JSON
var dictResponse DictResponse
//json.Unmarshal函数的作用是反序列化JSON字符串为Go语言中的结构体或map
//将bodyText中的JSON字符串反序列化为dictResponse这个DictResponse结构体实例
err = json.Unmarshal(bodyText, &dictResponse)
知识点总结:
1. 请求代码自动生成,JSON结构自动生成
2. 请求创建以及发起 `client := &http.Client{}` `resp, err := client.Do(req)`
3. JSON序列化以及反序列化的使用
SOCKES5代理服务器
简单TCP服务器程序的实现
整个程序是一个简单的回显服务器,接收客户端连接,读取客户端发送的数据并原样返回。
- Listen函数监听127.0.0.1:1080地址,等待客户端连接。
- 当有新的客户端连接时,Accept函数接受连接,并启动goroutine处理该连接。
- 在goroutine中,首先定义defer语句,确保函数结束时关闭连接。
- 然后创建一个bufio.Reader,用于高效读取数据。
- 在for循环中,不断读取客户端发送的单个字节,并直接发送回客户端。
- 如果任意一方断开连接,for循环会结束, defer语句关闭连接,函数返回。
package main
import (
"bufio" // 导入bufio包,提供带缓存的I/O操作
"log" // 导入log包,提供日志记录功能
"net" // 导入net包,提供网络相关的操作
)
func main() {
// 监听本地地址127.0.0.1:1080的TCP连接
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
}
// 启动一个新的goroutine处理客户端连接
go process(client)
}
}
func process(conn net.Conn) {
defer conn.Close() // 延迟关闭连接
reader := bufio.NewReader(conn) // 创建一个带缓存的读取器
for {
// 读取单个字节
b, err := reader.ReadByte()
if err != nil {
break
}
//println("form server get b=%v",b)
_, err = conn.Write([]byte{b}) // 将读取到的字节写回客户端
if err != nil {
break
}
}
}
主要使用了以下包:
- net - 用于网络连接的监听,读写等操作。
- bufio - 提供高效的IO函数,这里使用Reader读取数据。
- log - 用于打印日志信息。
关键函数:- Listen - 监听指定地址,等待连接。
- Accept - 接受新的客户端连接。
- ReadByte - 读取一个字节的数据。
- Write - 将数据写回给客户端。
- defer conn.Close - 关闭连接。
SOCKS5协议工作原理
-
协商(握手)阶段
第一个握手阶段,浏览器会向Socks5代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,Socks5服务器会选中一个认证方式,返回给浏览器。如果返回的是00的话就代表不需要认证,返回其他类型的话会开始认证流程;
-
认证阶段
-
请求阶段
认证通过之后浏览器会Socks5服务器发起请求。主要信息包括版本号,请求的类型,一般主要是Connection请求,就代表代理服务器要和某个域名或者某个IP地址某个端口建立TCP连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应
-
relay阶段
此时浏览器会发送正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到览器这边。然后实际上代理服务器并不关心流量的细节,可以是HTTP流量,也可以是其它TCP流量。
Socks5协议中的身份验证过程
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
// 读取协议版本号
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
// 如果协议版本号不是Socks5,则返回一个错误
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)
}
log.Println("ver", ver, "method", method)
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
Socks5协议中的连接请求过程
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
// 读取请求头
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 !=5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
addr := ""
switch atyp {
case atypIPV4:
// 解析IPv4地址
_, 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],[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:
// 解析IPv6地址
ipv6Addr := make([]byte, 16)
_, err = io.ReadFull(reader, ipv6Addr)
if err != nil {
return fmt.Errorf("read ipv6Addr failed:%w", err)
}
addr = net.IP(ipv6Addr).String()
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)
// 向客户端发送一个连接成功的响应
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本,这里为0x05
// REP Relay field,内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.PORT
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w",)
}
// 开始进行数据传输
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
}