上篇介绍了golang这门新的语言的一些语法。那么我们能用golang简单地写些什么代码出来呢?

一、猜数字

这个游戏的逻辑很简单。系统随机给你生成一个数,然后读取你猜的数字,再根据你猜的数字 跟系统生成的数字比较。告诉你结果这样。

(1)随机生成一个数

随机生成一个区间在1~100之间的数。

import math/rand
import time

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNum := rand.Intn(maxNum)
    fmt.Println("The secert number is:", secretNum)
}

用C\C++写过类似程序的朋友们,都会知道使用rand 的时候,如果不设置rand种子,那么生成的随机数是固定的。因此,我们import time,用time中的时间戳,设置rand的种子。


golang 实现 keepalived golang实战教程_服务器


(2)获取用户输入的值

生成要猜的数字是第一步,那么接下来要进行的就是获取用户输入的值。在go中有两个解法

fmt.Scanf;


golang 实现 keepalived golang实战教程_Powered by 金山文档_02


这个fmt.Scanf的使用和C中的scanf相差无几。也是最简单的获取值的方式。

bufio\os;

bufio:
Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer object, creating another object (Reader or Writer) that also implements the interface but provides buffering and some help for textual I/O.

bufio包实现IO缓冲。它包装了一个可读或者可写的io对象,并创建一个另一个实现接口的对象,为文本文件提供帮助和缓冲

如果有一定的linux系统的知识,对"linux下一切皆文件"这句话不陌生。在linux系统看来,我们的显示器是文件。程序获取用户的数据,是从键盘输入回显到显示器,再从显示器中拿到的。

os:
Package os provides a platform-independent interface to operating system functionality. The design is Unix-like, although the error handling is Go-like; failing calls return values of type error rather than error numbers. Often, more information is available within the error. For example, if a call that takes a file name fails, such as Open or Stat, the error will include the failing file name when printed and will be of type *PathError, which may be unpacked for more information.

简单来说,这个包是提供给用户,独立地操作系统功能的接口。


golang 实现 keepalived golang实战教程_golang_03

将从显示器的文件数据,创建另一个对象(reader)管理

golang 实现 keepalived golang实战教程_字符串_04

reader提供了ReadString的API接口。我试试看

golang 实现 keepalived golang实战教程_字符串_05

我们通过readString函数,把reader中的值给input。但是这个值不是很干净! 因为有"\r\n"。这个的存在是因为我们敲击了回车!为此,我们需要将input末尾的后缀给干掉!

golang 实现 keepalived golang实战教程_服务器_06

我们使用strings包里面TrimSuffix的这个函数,顾名思义,是一个过滤后缀的函数。

golang 实现 keepalived golang实战教程_json_07

(3)游戏逻辑判断

打印到显示器上的字符,到底是数字还是字符串?答案是字符串!我们得到的input的值,其实是一段自子串。因此,我们用到如TrimSuffix函数 是针对字符串类型的,而字符串的大小比较 和 整数的大小比较是不一样的!

strconv;

package strconv implements conversions to and from string representations of basic data types
包strconv实现基本数据类型的字符串表示形式之间的相互转换。

golang 实现 keepalived golang实战教程_json_08

golang 实现 keepalived golang实战教程_字符串_09


后面判断的逻辑也比较简单。也就不多言了。

(4)测试

package main

import (
    "fmt"
    "math/rand"
    "time"

    //系统接口
    "bufio"
    "os"

    //strings操作
    "strconv"
    "strings"
)

func main() {
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNum := rand.Intn(maxNum)
    fmt.Println("The secert number is:", secretNum)

    for {
        fmt.Println("Please Enter your guess~ ")
        // input := 0
        // fmt.Scanf("%d", &input)
        // fmt.Println(input)

        reader := bufio.NewReader(os.Stdin)
        //拆解
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("An input Error,Please reinput again", err)
            return
        }
        //fmt.Printf("Befor:%#v\n", input)
        input = strings.TrimSuffix(input, "\n")
        input = strings.TrimSuffix(input, "\r")
        //fmt.Printf("After:%#v\n", input)
        guess, err := strconv.Atoi(input)
        if err != nil {
            fmt.Println("Invalid input,Please Enter your guess again~", err)
            return
        }
        fmt.Println("Your guess is", guess)

        //逻辑判断
        if guess > secretNum {
            fmt.Println("More bigger")
        } else if guess < secretNum {
            fmt.Println("Less smaller")
        } else {
            fmt.Println("You are preety good,and correct: ", secretNum)
            break
        }
    }
}

golang 实现 keepalived golang实战教程_服务器_10

二、在线词典

我们准备通过http,得到一个英文单词的翻译。


golang 实现 keepalived golang实战教程_服务器_11


大概是这样的格式。那么制作这个肯定需要一定的网络基础。

(1)处理请求

main;

我们通过命令行,上传我们的请求。这里又要使用到我们的"老朋友" ——os package

golang 实现 keepalived golang实战教程_字符串_12


JSON;

JSON是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

//请求报头
type DictRequest struct{
    TransType string `json:"trans_type"`
    Source string `json:"source"`
    UserID string `json:"user_id"`
}

有了请求报头,接下来就需要应对对端返回的json数据应该如何解析。对于js/python这些脚本语言,有字典、map的概念,可以直接从json里面取值。但是对golang是不行的。需要golang创建一个结构体作为解析时的填充对象。

golang 实现 keepalived golang实战教程_json_13

网上有这样结构体代码的生成工具,要我们自己写肯定很麻烦。


golang 实现 keepalived golang实战教程_字符串_14


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"`
}

这样我们就得到了JSON结构体。解析http返回的报头。

net/http;

Package http provides HTTP client and server implementations.
包http提供了HTTP客户端和服务器实现。

client是http中的一个对象


golang 实现 keepalived golang实战教程_golang_15


golang 实现 keepalived golang实战教程_服务器_16


Marshal;


golang 实现 keepalived golang实战教程_Powered by 金山文档_17


golang 实现 keepalived golang实战教程_Powered by 金山文档_18

bytes;

Package bytes implements functions for the manipulation of byte slices. It is analogous to the facilities of the strings package.

实现了操作子串切片的操作。类似字符串包strings的功能

golang 实现 keepalived golang实战教程_Powered by 金山文档_19

golang 实现 keepalived golang实战教程_服务器_20

golang 实现 keepalived golang实战教程_Powered by 金山文档_21

(2)处理响应

golang 实现 keepalived golang实战教程_json_22

发送一个http请求,并返回一个http响应。

golang 实现 keepalived golang实战教程_字符串_23

而此时我们响应数据全在resp中。我们需要的是响应数据中的正文部分。

ioutil;

Package util contains utility code for use by volume plugins.

这个包提供一些好用的工具

golang 实现 keepalived golang实战教程_服务器_24


golang 实现 keepalived golang实战教程_字符串_25

但是,我们此时拿到的正文数据,是JSON格式的。我们为此需要将次填入我们为JSON事先创建好的结构体中。这个过程叫做,反序列化。

golang 实现 keepalived golang实战教程_Powered by 金山文档_26

golang 实现 keepalived golang实战教程_Powered by 金山文档_27


我们也就拿到返回的数据内容了。

(3)数据解析

golang 实现 keepalived golang实战教程_json_28

我们来看看效果吧~

golang 实现 keepalived golang实战教程_json_29


三、SOCKET5代理

(1)代理服务器简介

代理服务器(Proxy Server)的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站,是个人网络和Internet服务商之间的中间代理机构,负责转发合法的网络信息,对转发进行控制和登记。

SOCKS5 是一个代理协议,它在使用TCP/IP通讯的前端机器和服务器机器之间扮演一个中介角色,使得 内部网 中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过 TCP/IP协议 进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。 SOCKS5虽然是代理服务协议,但是不用作",它的协议是明文传输。

(2)SOCKS5原理


golang 实现 keepalived golang实战教程_Powered by 金山文档_30


(3)建立TCP

golang 实现 keepalived golang实战教程_Powered by 金山文档_31

package main

import (
    "log"
    "net"
)

func main() {
    server, err := net.Listen("tcp", "127.0.0.1:8801")
    if err != nil {
        log.Fatal(err)
    }

    for {
        client, err := server.Accept()
        if err != nil {
            log.Printf("Accept faild:%v", err)
            //没有链接到来持续等待即可
            continue
        }

        //连接到来
        go proceess(client)
    }
}

func proceess(conn net.Conn) {

}

goroutinue 可以类比一个进程里的子进程,但是开销小很多。

Process;

func proceess(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        b, err := reader.ReadByte()
        if err != nil {
            break
        }

        _, err = conn.Write([]byte{b})
        if err != nil {
            break
        }
    }
}

golang 实现 keepalived golang实战教程_服务器_32

defer这一行的含义,就是在函数结束时,释放掉资源。防止资源泄漏

接下来我们使用的bufio。我们在猜数字的时候用过。它为可读或可写的对象提供一个缓冲区。这里的IO输出是针对网络套接字,但是IO的速度是很慢的。因此,我们把网络数据 转换为bufio的形式,减少底层系统调用,同时,bufio也会提供一定的工具帮我们对数据进行获取。

golang 实现 keepalived golang实战教程_Powered by 金山文档_33

这里conn底层回去调用Write。conn保存了系统为这个连接打开的fd。

golang 实现 keepalived golang实战教程_Powered by 金山文档_34

golang 实现 keepalived golang实战教程_Powered by 金山文档_35

(4)认证阶段

代理服务器不仅仅是获取数据即可。否则我们刚刚完成一个TCP连接即可。

认证时,浏览器会发三个字段给代理服务器:version、methods、methods编码

golang 实现 keepalived golang实战教程_json_36

version;

golang 实现 keepalived golang实战教程_golang_37

method;

golang 实现 keepalived golang实战教程_golang_38

它返回复制的字节数,如果读取的字节数较少,则返回一个错误。

golang 实现 keepalived golang实战教程_golang_39

这样我们也就获取了version与method;此时,代理服务器需要向 浏览器返回两个字段,version\method;


golang 实现 keepalived golang实战教程_json_40


(5)请求阶段

在这个阶段,我们就需要拿到浏览器url里面访问的IP+PORT,定位服务器。此时我们增加了一个connect函数帮我们完成这个功能

golang 实现 keepalived golang实战教程_golang_41

golang 实现 keepalived golang实战教程_Powered by 金山文档_42

atyp地址类别;

如果是IPV4,我们逐个将这刚好4字节打印成IP地址的格式。

如果是host,我们则需要重新计算大小,转换成字符串并保存。

IPV6这里算了。用得少。

golang 实现 keepalived golang实战教程_json_43

端口port;

我们日常生活中的有大端机,小端机。所谓大小端机最大的差别,就在于存储字符的字节序不同。为了屏蔽掉这个差异,网络中统一发送的字节序为大端字节序。如果是小端机接收,那么你就需要进行转换,如果是大端,那就什么都可以不做。

golang 实现 keepalived golang实战教程_Powered by 金山文档_44

(6)Relay阶段

golang 实现 keepalived golang实战教程_Powered by 金山文档_45

golang 实现 keepalived golang实战教程_Powered by 金山文档_46

此时两端主机正式开始建立连接了。

但是有个问题是,一旦执行到这里,connect函数就会结束。那么两个端的连接也就会被关闭。因此,什么时候应该是将两端连接关闭呢? 一旦其中一方出现copy错误时~

golang 实现 keepalived golang实战教程_json_47

context;

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

包上下文定义了上下文类型,它携带截止日期、取消信号和其他跨API边界和进程间的请求范围的值。

golang 实现 keepalived golang实战教程_服务器_48

当调用cancel()的时候, 处于等待的ctx.Done()会立即返回。

golang 实现 keepalived golang实战教程_服务器_49

以上也就简简单单用golang做了一些小的代码。