这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记。

文章目录

  • 语法速览
    • 基础语法
      • 第一:类型
      • 第二:内置库部分
        • json库的使用
        • 时间库的使用
        • 字符串和数字互转
        • os相关信息
  • 实战项目
    • 猜谜游戏(pass,过于简单)
    • 在线词典
      • 第一步:抓包得得到数据进行分析
      • 第二步:利用工具生成代码
        • curl请求直接转为go的请求代码
        • JSON转Golang Struct
      • 第三步:更改代码实现功能
      • homework
    • SOCKS5代理服务器
      • SOCKS5简单介绍
      • SOCKS5代理原理
      • 具体实现
        • v1-简单echo服务器
        • v2-实现SOCKS5的握手阶段
        • v3-实现SOCKS5的请求阶段
        • v4-实现SOCKS5的转发阶段(最终完全版本
        • 验证

语法速览

基础语法

基础语法有几点需要注意:

第一:类型

有值类型,有指针,指针只能作为引用的替代品,无法指针直接运算。

go语言有值类型,可以直接像下面这样定义变量:

type Student struct {name stringsid  string
}
func main(){var student = Student{name: "John", sid: "1001"}   //student为值类型var student = &Student{name: "John", sid: "1001"} //student为指针类型(注意由于go有垃圾回收机制,所以这里会自动为我们开辟堆内存student := new(Student) //也可通过内置的new()函数直接开辟堆内存,而不立马初始化,得到一个指针
}

go语言的切片

同样切片类型也有上述两种获得内存的定义方式,也可通过内置的make函数对内部的cap和len进行初始的控制。

nums := make([]int,2,10)//得到一个底层数组长度为2,cap为10的初始切片
nums1 := nums2[0:3] //第二种切片方式

第二:内置库部分

json库的使用

通过在字段后面跟着的字符串进行序列化的定义,后面跟着的称为域标签。

package mainimport ("encoding/json""fmt"
)type Student struct {Name string `json:"name"`Sid  string `json:"sid"`
}func main() {s := Student{Name: "jonh" ,Sid: "10323"}//序列化p ,err := json.Marshal(s)if err!=nil {panic(err)}fmt.Println(string(p))//反序列化err = json.Unmarshal(p,&s)if err!=nil {panic(err)}fmt.Println(s)
}

官方对域标签有以下说明:

// Field appears in JSON as key "myName".
Field int `json:"myName"`// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`// Field is ignored by this package.
Field int `json:"-"`// Field appears in JSON as key "-".
Field int `json:"-,"`
时间库的使用

时间的获取

获取当前时间:

package mainimport ("fmt""time"
)func main() {now := time.Now() //获取当前时间fmt.Printf("current time:%v\n", now)year := now.Year()     //年month := now.Month()   //月day := now.Day()       //日hour := now.Hour()     //小时minute := now.Minute() //分钟second := now.Second() //秒fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

获取时间戳

package mainimport ("fmt""time"
)func main() {now := time.Now()            //获取当前时间timestamp1 := now.Unix()     //时间戳timestamp2 := now.UnixNano() //纳秒时间戳fmt.Printf("现在的时间戳:%v\n", timestamp1)fmt.Printf("现在的纳秒时间戳:%v\n", timestamp2)
}

时间戳与时间的转换

package mainimport ("fmt""time"
)func main() {now := time.Now()                  //获取当前时间timestamp := now.Unix()            //时间戳timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式fmt.Println(timeObj)year := timeObj.Year()     //年month := timeObj.Month()   //月day := timeObj.Day()       //日hour := timeObj.Hour()     //小时minute := timeObj.Minute() //分钟second := timeObj.Second() //秒fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

获取星期几

package mainimport ("fmt""time"
)func main() {t := time.Now()fmt.Println(t.Weekday().String())
}

时间的操作

(1)Add(during)函数实现某个时间 + 时间间隔

package main
import ("fmt""time"
)
func main() {now := time.Now()later := now.Add(time.Hour) // 当前时间加1小时后的时间fmt.Println(later)
}

(2)Sub(Time)获取时间差值

返回一个时间段 t - u 的值。如果结果超出了 Duration 可以表示的最大值或最小值,将返回最大值或最小值,要获取时间点 t - d(d 为 Duration),可以使用 t.Add(-d)。

(3)Equal(Time)判断时间是否相同

(4)Before 和 After某个时间是否在他之前或之后

定时任务

使用 time.Tick(时间间隔) 可以设置定时器,定时器的本质上是一个通道(channel)

package main
import ("fmt""time"
)
func main() {ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器for i := range ticker {fmt.Println(i) //每秒都会执行的任务}
}

解析字符串格式的时间

Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。

func Parse(layout, value string) (Time, error)

与 Parse 函数类似的还有 ParseInLocation 函数。

func ParseInLocation(layout, value string, loc *Location) (Time, error)

ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处:

  • 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
  • 第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。

示例代码如下:

package main
import ("fmt""time"
)
func main() {var layout string = "2006-01-02 15:04:05"var timeStr string = "2019-12-12 15:22:12"timeObj1, _ := time.Parse(layout, timeStr)fmt.Println(timeObj1)timeObj2, _ := time.ParseInLocation(layout, timeStr, time.Local)fmt.Println(timeObj2)
}
字符串和数字互转

字符串与数字互转的想关库函数全在一个包内:strconv包

一图胜千言:

os相关信息

os包里面封装了很多和操作系统相关的内容。

如下:

package mainimport ("fmt""os""os/exec"
)
func main() {fmt.Println(os.Args) //打印命令行参数fmt.Println(os.Getenv("PATH")) //打印环境变量fmt.Println(os.Setenv("AA","BB")) //设置环境变量,key,val形式设置buf,err := exec.Command("grep").CombinedOutput() //执行cmd命令if err != nil {panic(err)}fmt.Println(string(buf))
}

其他pass跳过

实战项目

猜谜游戏(pass,过于简单)

在线词典

想要实现在线词典,首先就得用到别人的翻译引擎

第一步:抓包得得到数据进行分析

以彩云词典为例:

从网页调试工具里面查看随时收发的网络数据包,挨个查看它们的response,如果里面的json数据出现翻译结果,那么说明这个包就是返回的翻译结果!

那么我们只需要让go语言来做同样的两件事:

  1. 发起请求。
  2. 解析返回的json内容。

只要做好了这两件事,那么就很快得到了一个单词的翻译了。

第二步:利用工具生成代码

在此之前我们需要清楚有两个神器般存在的网站:

  1. curlconverter 把curl请求直接转为go的请求代码。
  2. oktools JSON转Golang Struct

那么我们先肯定是要得到请求的代码,然后稍作更改,解析body后得出想要的结果。

curl请求直接转为go的请求代码

如下图进到刚才我们捕捉到的目标包,然后复制cURL,到网站进行解析得到最终代码。

package mainimport ("fmt""io/ioutil""log""net/http""strings"
)func main() {client := &http.Client{}var data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`)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("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`)req.Header.Set("sec-ch-ua-mobile", "?0")req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 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("os-type", "web")req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")req.Header.Set("sec-ch-ua-platform", `"Windows"`)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")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)}fmt.Printf("%s\n", bodyText)
}

观察代码的改变我们只需对source部分的内容进行更改,即可得到对应的翻译结果。

JSON转Golang Struct

得到翻译结果,body内容后,我们需要把body内容解析为本地的sturct才能正常使用(当然你头铁的话可以直接找对应的字符串即可,也不需要反序列化。

type AutoGenerated 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 []interface{} `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"`
}

第三步:更改代码实现功能

通过前面生成的代码已经能够实现请求和接收响应,且可以直接把响应内容反序列化为结构体,那么接下来,只需要把想要的部分遍历打印即可。

最终代码如下:

package srcimport ("bytes""encoding/json""fmt""io/ioutil""log""net/http"
)func QueryCaiyun(word string) {client := &http.Client{}request := DictRequestCaiyun{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("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`)req.Header.Set("sec-ch-ua-mobile", "?0")req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 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("os-type", "web")req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")req.Header.Set("sec-ch-ua-platform", `"Windows"`)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")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 Status code:", resp.StatusCode, "body", string(bodyText))}var dictResponse DictResponseCaiyunerr = json.Unmarshal(bodyText, &dictResponse)if err != nil {log.Fatal(err)}fmt.Println("translate from Caiyun\n", "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)for _, item := range dictResponse.Dictionary.Explanations {fmt.Println(item)}
}

homework

后面我依次通过这个方式还弄了其他的翻译引擎,但是有很多翻译引擎的请求头都是动态实时的,或者加了密。我做的第二个有道的翻译引擎使用就是动态实时的,然后去查阅了破解方法,发现是通过阅读原本的js源码进行反推,得出请求头里面动态变化的内容。

最终写了Deepl和有道两个搜索引擎。

源码实现链接:https://github.com/ACking-you/TraningCamp/tree/master/lesson1/homework/simple_dict/src

SOCKS5代理服务器

SOCKS5简单介绍

SOCKS5是代理协议,在使用TCP/IP协议通信的前端机器和服务器之间发挥中介作用,使内部网络的前端机器能够访问互联网的服务器,使通信更加安全。SOCKS5服务器将前端发送的请求转发给真正的目标服务器,模拟前端行为。此处,前端与SOCKS5之间也是通过TCP/IP协议进行通信的,前端向SOCKS5服务器发送请求发送给SOCKS5服务器,然后SOCKS5服务器将请求发送给真正的服务器。SOCKS5服务器在将通讯请求发送给真正服务器的过程中,对于请求数据包本身不加任何改变(明文传输)。SOCKS5服务器在收到真实服务器响应后,也原样转发到前端。

它的用途是, 比如某些企业的内网为了确保安全性,有很严格的防火墙策略,但是带来的副作用就是访问某些资源会很麻烦。 socks5 相当于在防火墙开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。实际上很多翻墙软件,最终暴露的也是一个 socks5 协议的端口。

SOCKS5代理原理

正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立 TCP 连接,然后三次握手,握手完之后发起 HTTP 请求,然后服务返回 HTTP 响应。如果设置代理服务器之后,流程会变得复杂一些。 首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、 relay 阶段

  • 握手阶段:浏览器会向 socks5 代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5 服务器会选中一个认证方式,返回给浏览器。如果返回的是 00 的话就代表不需要认证,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。(本次课程跳过认证阶段)
  • 请求阶段:认证通过之后浏览器会对 socks5 服务器发起请求。主要信息包括 版本号,请求的类型,一般主要是 connection 请求,就代表代理服务器要和某个域名或者某个 IP 地址某个端口建立 TCP 连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。
  • relay 阶段:此时浏览器会发送 正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上 代理服务器并不关心流量的细节,可以是 HTTP流量,也可以是其它 TCP 流量。 这个就是 socks5 协议的工作原理。
#mermaid-svg-h4kKN3WWDW40ngOB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .error-icon{fill:#552222;}#mermaid-svg-h4kKN3WWDW40ngOB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h4kKN3WWDW40ngOB .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-h4kKN3WWDW40ngOB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h4kKN3WWDW40ngOB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h4kKN3WWDW40ngOB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h4kKN3WWDW40ngOB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h4kKN3WWDW40ngOB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h4kKN3WWDW40ngOB .marker.cross{stroke:#333333;}#mermaid-svg-h4kKN3WWDW40ngOB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h4kKN3WWDW40ngOB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .cluster-label text{fill:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .cluster-label span{color:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .label text,#mermaid-svg-h4kKN3WWDW40ngOB span{fill:#333;color:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .node rect,#mermaid-svg-h4kKN3WWDW40ngOB .node circle,#mermaid-svg-h4kKN3WWDW40ngOB .node ellipse,#mermaid-svg-h4kKN3WWDW40ngOB .node polygon,#mermaid-svg-h4kKN3WWDW40ngOB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h4kKN3WWDW40ngOB .node .label{text-align:center;}#mermaid-svg-h4kKN3WWDW40ngOB .node.clickable{cursor:pointer;}#mermaid-svg-h4kKN3WWDW40ngOB .arrowheadPath{fill:#333333;}#mermaid-svg-h4kKN3WWDW40ngOB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h4kKN3WWDW40ngOB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h4kKN3WWDW40ngOB .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-h4kKN3WWDW40ngOB .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-h4kKN3WWDW40ngOB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h4kKN3WWDW40ngOB .cluster text{fill:#333;}#mermaid-svg-h4kKN3WWDW40ngOB .cluster span{color:#333;}#mermaid-svg-h4kKN3WWDW40ngOB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h4kKN3WWDW40ngOB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

握手阶段
认证阶段
请求阶段
转发relay阶段

具体实现

v1-简单echo服务器
package mainimport ("bufio""fmt""log""net"
)func main() {server, err := net.Listen("tcp", "0.0.0.0:1080")if err != nil {panic(err)}for {client, err := server.Accept()if err != nil {log.Printf("Accept failed %v", err)continue}fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr())go process(client)}
}func process(conn net.Conn) {defer func() {conn.Close()fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr())}()//用缓冲流进行一次包装,减少底层IO次数,让读取效率更高效reader := bufio.NewReader(conn)for {b, err := reader.ReadByte()if err != nil {break}_, err = conn.Write([]byte{b})if err != nil {break}}
}

客户端验证:

没必要再写一个客户端,这时完全可以netcat工具进行tcp连接测试。

如下:

v2-实现SOCKS5的握手阶段

实现SOCKS5之前我们需要清楚SOCKS5的握手阶段的请求和返回是怎么样的,如下面的图表所示:

VER NMETHODS METHODS
1byte 1byte 1 to 255 byte
协议版本信息socks5为0x05 支持认证的方法数量值为0x00则表示无需认证 NMETHODS的值为多少METHODS就有多少个字节
package main//auth 阶段
import ("bufio""fmt""io""log""net"
)
const(socks5Ver = 0x05cmdBind = 0x01atypIPV4 = 0x01atypeHOST = 0x03atypeIPV6 = 0x04
)
func main() {server, err := net.Listen("tcp", "0.0.0.0:1080")if err != nil {panic(err)}for {client, err := server.Accept()if err != nil {log.Printf("Accept failed %v", err)continue}fmt.Printf("连接成功! clent:%v \n", client.RemoteAddr())go process(client)}
}func process(conn net.Conn) {defer func() {conn.Close()fmt.Printf("连接断开! clent:%v \n", conn.RemoteAddr())}()reader := bufio.NewReader(conn)err := auth(reader,conn)if err!=nil{log.Printf("client %v auth failed:%v",conn.RemoteAddr(),err)}log.Println("auth success")
}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)}log.Println("ver",ver,"method",method)//返回的内容表示SOCKS5通信,且无需认证_,err = conn.Write([]byte{socks5Ver,0x00})if err !=nil{return fmt.Errorf("write failed:%w",err)}return nil
}
v3-实现SOCKS5的请求阶段

同样来看看此时的消息协议:

客户端请求:

VER CMD RSV ATYP DST.ADDR DST.PORT
1byte 1byte 1byte 1byte Variable 2byte
协议版本0x05为SOCKS5 代表请求类型0x01表示CONNECT请求 保留字段(不理会) 目标地址类型(IPV4/IPV6/域名) 地址值,根据不同地址类型,长度不同 需要访问的服务器的端口号

服务端响应:

VER REP RSV ATYP BIND.ADDR BIND.PORT
1byte 1byte 1byte 1byte Variable 2byte
协议版本0x05为SOCKS5 代表响应。成功就返回0 保留字段(不理会) 地址类型(IPV4/IPV6/域名) 地址值(这里暂时不需要 端口号(这里暂时不需要

这一过程的代码:

对当前的实现进行测试:

进行如下curl命令:

curl --socks5 localhost:1080 -v http://www.qq.com

此时请求会失败,但我们已经能看到正常打印出来的ip和端口号

v4-实现SOCKS5的转发阶段(最终完全版本

最后的转发过程,由于不需要对流量进行任何的处理,所以没有上层协议,直接再Write操作完后把流量进行转发即可。

对于两个连接流量的转发,标准库里有有一些好用的函数库。

  1. 通过net.Dial建立tcp连接。

    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()
    
  2. 标准库的 io.copy 可以实现一个单向数据转发,双向转发的话,需要启动两个 goroutinue。

    go func() {_, _ = io.Copy(dest, reader)cancel()
    }()
    go func() {_, _ = io.Copy(conn, dest)cancel()
    }()
    

现在有一个问题,connect 函数会立刻返回,返回的时候连接就被关闭了。需要等待任意一个方向copy出错的时候,再返回 connect 函数。

这个context目前还弄不明白

这里可以使用到标准库里面的一个 context 机制,用 context 连 with cancel 来创建一个context。 在最后等待 ctx.Done() , 只要 cancel 被调用, ctx.Done就会立刻返回。 然后在上面的两个 goroutinue 里面 调用一次 cancel 即可。

验证

同样是以下请求命令此时终于返回正常内容了!

curl --socks5 localhost:1080 -v http://www.qq.com

现在SOCKS5代理服务器程序已经写好,可以使用SwitchyOmega插件对该代理服务器进行正式的使用(可以用连接了学校内网的电脑,作为SOCKS5代理服务器对学校内网的内容进行访问( ̄▽ ̄)"

Go语言上手(一) | 青训营笔记相关推荐

  1. 三个小项目入门Go语言|字节青训营笔记

    前言 这是青训营的第一课,今天的课程比较快速的讲解了go语言的入门,并配合三个小的项目实践梳理所学知识点,这里详细回顾一下这三个项目,结合课后作业要求做一些代码补充,并附上自己的分析,青训期间的所有课 ...

  2. Go语言实战案例 | 青训营笔记

    这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天 前言 在上一篇笔记中,已经总结了 Go 语言的基础语法和一些常见标准库的使用方法,本文主要基于之前的基础,通过两个实战的例子对 Go 的基础语 ...

  3. HTML+CSS实现按钮手风琴效果 | 青训营笔记

    HTML+CSS实现按钮手风琴效果 | 青训营笔记 需求描述 HTML部分 CSS部分 完整代码 效果展示 这是我参与「第四届青训营」笔记创作活动的的第7天. 需求描述 实现一排居中的五个图标,默认为 ...

  4. 【Go入门】Socks5代理服务器实现 翻译api调用 | 青训营笔记

    这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记. [课程一]Go基础 主要内容 第一节课主要介绍了go的语法基础,包括变量.循环分支.数组切片.函数方法.错误处理.字符串.格式化处理. ...

  5. 字节跳动青训营笔记01

    大家好,这里是我参加字节跳动[第五期青训营]的第一篇笔记.一小时多的课程里实现了快速入门Go语言,简要介绍了Go的优点,语法,包括与C语言的异同,以及map.切片等常用的数据结构:后半节课程主要介绍了 ...

  6. Android基础第六天 | 字节跳动第四届青训营笔记

    这是我参与「第四届青训营 」笔记创作活动的的第6天 端智能技术演进与实践 文章目录 端智能技术演进与实践 课程回顾 01 什么是端智能技术 1.1 端智能是什么? 1.2 为什么要做端智能? 1.3 ...

  7. 构建webpack知识体系 | 青训营笔记

    这是我参与「第四届青训营 」笔记创作活动的的第4天. 进阶中高级前端工程师,对webpack打包构建工具的掌握是必不可少的.我也曾经看过许多webpack教程,但无非是记记某些模式怎么配置,根本就没有 ...

  8. GO语言的实战学习(猜谜游戏和在线词典)| 青训营笔记

    一.GO语言的实战学习 1.1 前言 在上文我们急速学习了Go语言的入门,今天我们来学习一下Go语言的实战 本专栏代码,源码打包下载地址如下: https://download.csdn.net/do ...

  9. 存储与数据库 | 字节青训营笔记

    目录 一.存储系统 1.什么是存储系统 2.存储系统的特点 3.RAID技术 RAID出现的背景 RAID 0 RAID 1 RAID 0+1 二.数据库 1.难道数据库和存储系统不一样吗 2.数据库 ...

最新文章

  1. IOS中CoreLocation框架地理定位
  2. 【数据分析】太秀了!用Excel也能实现和Python数据分析一样的功能!
  3. 0709-To Lower Case(转换成小写字母)
  4. 食物链 POJ - 1182
  5. django 模板mysql_59 Django基础三件套 , 模板{{}}语言 , 程序连mysql Django项目app Django中ORM的使用...
  6. mysql linux_linux下mysql下载安装
  7. 模拟实现请求分页虚存页面替换算法_河北串口屏厂家:玻璃清洗机触摸屏实现数据交互功能...
  8. maven常见问题归纳
  9. 2018软工实践第五次作业——结对作业2
  10. JForum3 学习笔记1
  11. Atitit Immutability 和final的优点
  12. 最全企业安全网管工具名单!
  13. HTML5网页扫描二维码
  14. xposed框架修改手机串号教程_xposed 入门之修改手机 IMEI
  15. 安装Win 8.1 跳过输入密钥步骤
  16. 求助:如何实现EA自动判断趋势,寻找高低点,并获取高低点K线价格数据,然后根据价格画出支撑阻力区域.
  17. python的伪代码怎么写_如何写伪代码 - Take-it-and-apply-it - 博客园
  18. 在AWS上的架构部署与设计
  19. 电子信息工程要考研吗?
  20. 2、通过mos管构成的逻辑门电路

热门文章

  1. 计算机二级和三级哪个厉害,计算机二级和三级哪个更有用啊?没
  2. # 2023 好用免费图床推荐
  3. MySQL 根据某一个或者多个字段查找重复数据
  4. 将台式电脑声音无线传输到IOS,安卓手机上播放,再到蓝牙音箱上播放
  5. C# treeView 点击问题
  6. 希腊字母在数学计算中表示的含义
  7. 数学建模美赛写作指导20篇(一)-美赛数学专业词汇
  8. 科技风PPT模板百度网盘下载
  9. 在最好的白银要趋避的交易心理陷阱
  10. SAP 各大常用模块汇总介绍(三) | 易拓科技