• Go 语言实战案例:猜谜游戏&在线词典&SOCKS5代理服务器 Go学习路线


    字节跳动后端入门 - Go 语言原理与实践& vscode配置安装Go


    3.1猜谜游戏

    3.1.2 生成随机数v2

    1. package main
    2. import (
    3. "fmt"
    4. "math/rand"
    5. "time"
    6. )
    7. func main() {
    8. maxNum := 100
    9. rand.Seed(time.Now().UnixNano())
    10. secretNumber := rand.Intn(maxNum)
    11. fmt.Println("The secret number is ", secretNumber)
    12. }

    3.1.3 读取用户输入

    1. package main
    2. import (
    3. "bufio"
    4. "fmt"
    5. "math/rand"
    6. "os"
    7. "strconv"
    8. "strings"
    9. "time"
    10. )
    11. func main() {
    12. maxNum := 100
    13. rand.Seed(time.Now().UnixNano())
    14. secretNumber := rand.Intn(maxNum)
    15. fmt.Println("The secret number is ", secretNumber)
    16. fmt.Println("Please input your guess")
    17. reader := bufio.NewReader(os.Stdin) //转成 只读 流
    18. input, err := reader.ReadString('\n') //读取一行输入
    19. if err != nil {
    20. fmt.Println("An error occured while reading input. Please try again", err)
    21. return
    22. }
    23. input = strings.Trim(input, "\r\n") //去掉换行符
    24. guess, err := strconv.Atoi(input) //转换成数字
    25. if err != nil { //转换失败,则打印错误信息
    26. fmt.Println("Invalid input. Please enter an integer value")
    27. return
    28. }
    29. fmt.Println("You guess is", guess)
    30. }

    3.1.4 实现判断逻辑

    3.1.5 实现游戏循环

    3.2 在线词典介绍

    这个实例中我们将学习:

    如何用Go语言发送http请求、解析JSON,还会学习如何使用代码生成提高效率

    3.2.1 抓包

    3.2.1 代码生成

    想办法在Go里面发送这个请求!

    有几个Header比较复杂 转义导致的编译错误

    3.2.2 生成代码解读

    转换成流 异常 ;

    defer手动关闭流 defer会在函数结束之后 从下往上触发 防止泄露

    3.2.3 生成request body

    3.2.4 解析response body

    点击“转换-嵌套” 生成的代码会紧凑一点

    3.2.5 打印结果

    3.2.6 完善代码


    3.3 SOCKS5代理服务器

    SOCKS5代理协议 明文不能用来翻墙
    早期用于
    某些企业的内网搭建过于安全导致管理员访问某些资源也很麻烦
    这个协议相当于在防火墙内部开了个口子 使用户通过单个端口可以访问所有资源

    3.3.0 SOCKS5协议工作原理

    正常浏览器访问网站 不经过代理服务器 要建立TCP链接,进行三次握手 发起http请求,服务器返回http响应;

    设置代理服务器

    1.用户浏览器和socks5代理服务器进行链接,发送报文请求;

    1.1代理服务器协商 建议浏览器用哪种请求方式建立链接 ;

    2.发送请求; 2.1 代理服务器和真正的服务器进行TCP链接;

    3.浏览器 发送数据;

    3.3.1 简版TCP echo server

    逻辑:给他发送啥 就回复啥

    接受请求 成功的话返回一个链接;

    go process函数(其中go类似线程)里面处理这个链接 关闭链接 基于链接创建只读缓冲流

    死循环 ReadByte()读一字节;这个读看上去是1字节;但是又因为缓冲流,所以在底层会做一个合并,提前读1kb,然后再for里读1字节 Write()写入字节 出错的话就break关闭链接

    3.3.2 auth-实现协议的第一步-认证阶段

    3.3.3 请求阶段

    auth函数类似的connect函数;

    浏览器在请求阶段发送报文 包括6个阶段

    1. package main
    2. import (
    3. "bufio"
    4. "context"
    5. "encoding/binary"
    6. "errors"
    7. "fmt"
    8. "io"
    9. "log"
    10. "net"
    11. )
    12. const socks5Ver = 0x05
    13. const cmdBind = 0x01
    14. const atypeIPV4 = 0x01
    15. const atypeHOST = 0x03
    16. const atypeIPV6 = 0x04
    17. func main() {
    18. server, err := net.Listen("tcp", "127.0.0.1:1080")
    19. if err != nil {
    20. panic(err)
    21. }
    22. for {
    23. client, err := server.Accept()
    24. if err != nil {
    25. log.Printf("Accept failed %v", err)
    26. continue
    27. }
    28. go process(client)
    29. }
    30. }
    31. func process(conn net.Conn) {
    32. defer conn.Close()
    33. reader := bufio.NewReader(conn)
    34. err := auth(reader, conn)
    35. if err != nil {
    36. log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
    37. return
    38. }
    39. err = connect(reader, conn)
    40. if err != nil {
    41. log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
    42. return
    43. }
    44. }
    45. func auth(reader *bufio.Reader, conn net.Conn) (err error) {
    46. // +----+----------+----------+
    47. // |VER | NMETHODS | METHODS |
    48. // +----+----------+----------+
    49. // | 1 | 1 | 1 to 255 |
    50. // +----+----------+----------+
    51. // VER: 协议版本,socks5为0x05
    52. // NMETHODS: 支持认证的方法数量
    53. // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
    54. // X’00’ NO AUTHENTICATION REQUIRED
    55. // X’02’ USERNAME/PASSWORD
    56. ver, err := reader.ReadByte()
    57. if err != nil {
    58. return fmt.Errorf("read ver failed:%w", err)
    59. }
    60. if ver != socks5Ver {
    61. return fmt.Errorf("not supported ver:%v", ver)
    62. }
    63. methodSize, err := reader.ReadByte()
    64. if err != nil {
    65. return fmt.Errorf("read methodSize failed:%w", err)
    66. }
    67. method := make([]byte, methodSize)
    68. _, err = io.ReadFull(reader, method)
    69. if err != nil {
    70. return fmt.Errorf("read method failed:%w", err)
    71. }
    72. // +----+--------+
    73. // |VER | METHOD |
    74. // +----+--------+
    75. // | 1 | 1 |
    76. // +----+--------+
    77. _, err = conn.Write([]byte{socks5Ver, 0x00})
    78. if err != nil {
    79. return fmt.Errorf("write failed:%w", err)
    80. }
    81. return nil
    82. }
    83. func connect(reader *bufio.Reader, conn net.Conn) (err error) {
    84. // +----+-----+-------+------+----------+----------+
    85. // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
    86. // +----+-----+-------+------+----------+----------+
    87. // | 1 | 1 | X'00' | 1 | Variable | 2 |
    88. // +----+-----+-------+------+----------+----------+
    89. // VER 版本号,socks5的值为0x05
    90. // CMD 0x01表示CONNECT请求
    91. // RSV 保留字段,值为0x00
    92. // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。//重点关注部分
    93. // 0x01表示IPv4地址,DST.ADDR为4个字节
    94. // 0x03表示域名,DST.ADDR是一个可变长度的域名
    95. // DST.ADDR 一个可变长度的值
    96. // DST.PORT 目标端口,固定2个字节
    97. buf := make([]byte, 4) //创建一个长度为4的缓冲区
    98. _, err = io.ReadFull(reader, buf) //readfull填充满
    99. if err != nil {
    100. return fmt.Errorf("read header failed:%w", err)
    101. }
    102. ver, cmd, atyp := buf[0], buf[1], buf[3]//验证这四个字段的合法性
    103. if ver != socks5Ver {
    104. return fmt.Errorf("not supported ver:%v", ver)
    105. }
    106. if cmd != cmdBind {
    107. return fmt.Errorf("not supported cmd:%v", cmd)
    108. }
    109. addr := ""
    110. switch atyp {//不同的atype 类型走不同case分支
    111. case atypeIPV4:
    112. _, err = io.ReadFull(reader, buf)
    113. if err != nil {
    114. return fmt.Errorf("read atyp failed:%w", err)
    115. }
    116. //打印成ip地址 xxx.xxx...
    117. addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
    118. case atypeHOST://HOST类型
    119. hostSize, err := reader.ReadByte()
    120. if err != nil {
    121. return fmt.Errorf("read hostSize failed:%w", err)
    122. }
    123. host := make([]byte, hostSize)
    124. _, err = io.ReadFull(reader, host)
    125. if err != nil {
    126. return fmt.Errorf("read host failed:%w", err)
    127. }
    128. addr = string(host)//转换为字符串
    129. case atypeIPV6://用的比较少 暂时不实现
    130. return errors.New("IPv6: no supported yet")
    131. default:
    132. return errors.New("invalid atyp")
    133. }
    134. _, err = io.ReadFull(reader, buf[:2])//切片语法
    135. if err != nil {
    136. return fmt.Errorf("read port failed:%w", err)
    137. }
    138. port := binary.BigEndian.Uint16(buf[:2])
    139. dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
    140. if err != nil {
    141. return fmt.Errorf("dial dst failed:%w", err)
    142. }
    143. defer dest.Close()
    144. log.Println("dial", addr, port)//打印日志
    145. // +----+-----+-------+------+----------+----------+
    146. // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
    147. // +----+-----+-------+------+----------+----------+
    148. // | 1 | 1 | X'00' | 1 | Variable | 2 |
    149. // +----+-----+-------+------+----------+----------+
    150. // VER socks版本,这里为0x05
    151. // REP Relay field,内容取值如下 X’00’ succeeded
    152. // RSV 保留字段
    153. // ATYPE 地址类型
    154. // BND.ADDR 服务绑定的地址
    155. // BND.PORT 服务绑定的端口DST.PORT
    156. _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    157. if err != nil {
    158. return fmt.Errorf("write failed: %w", err)
    159. }//按照协议 给一个返回包
    160. ctx, cancel := context.WithCancel(context.Background())
    161. defer cancel()
    162. go func() {
    163. _, _ = io.Copy(dest, reader)
    164. cancel()
    165. }()
    166. go func() {
    167. _, _ = io.Copy(conn, dest)
    168. cancel()
    169. }()
    170. <-ctx.Done()
    171. return nil
    172. }

    3.3.4 relay阶段 和真正的服务建立TCP链接

    net.Dial函数 简单的建立tcp链接;

    io.copy 实现单向数据转发 把参数src的数据死循环的拷贝到dst里

    用户浏览器到底层服务器 反向 ;context.withconce创建context ;182行等到ctx.Done()即context执行完成也就是cancel函数()被调用; cancel()在两个方向任一方向的拷贝出错时被调用;

    3.3.5 实现成功

    Chrome浏览器下载插件SwitchyOmega

    入门总结

  • 相关阅读:
    木耳炒什么好吃 木耳的做法
    <scope>compile</scope>在dependency中有什么用?
    启山智软/怎么选择可靠的电商商城系统
    C++(11):enable_shared_from_this
    NetSuite BOM材料产出率舍入
    Oracle通过局域网进行连接访问的设置
    Git问题 “fatal: Could not read from remote repository.“
    mysql高级SQL 语句
    微信小程序python+uniapp+hbuiderx宠物美容用品商城领养寄养系统i843n
    Python 正则表达式大全,值得收藏
  • 原文地址:https://blog.csdn.net/m0_67184231/article/details/130903374