国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器

這篇具有很好參考價值的文章主要介紹了【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

**本人是第六屆字節(jié)跳動青訓營(后端組)的成員。本文由博主本人整理自該營的日常學習實踐,首發(fā)于稀土掘金:??Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器 | 青訓營

我的go開發(fā)環(huán)境:

*本地IDE:GoLand 2023.1.2

*go:1.20.6

一、猜謎游戲

猜數字游戲也算是入門一門編程語言必寫的程序了。通過這個程序,我們可以熟悉Go語言中的輸入輸出、流程控制與隨機函數的調用。

1、生成隨機數

在Go語言中,標準庫math/rand下的一系列方法可以用來生成隨機數。

math/rand庫的官方文檔:https://pkg.go.dev/math/rand

通過調用庫中的rand.Intn(n)函數,可以生成一個 [0,n) 的隨機整數:

//Go 1.20
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    maxNum := 100
    for i := 0; i < 10; i++ {    //生成10個隨機整數
       secretNum := rand.Intn(maxNum)        //每次生成的隨機數在 [0,100)
       fmt.Println("隨機數是", secretNum)
    }
}

注意:生成的這10個隨機整數是否相同呢?

在我所使用的 Go 1.20.6 下,生成的10個隨機數是各不相同的。也就是說,從 Go 1.20 開始,rand.Intn()生成的是真隨機數,不需要設定Seed()。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

依照官方的說法,math/rand 已棄用 rand.Seed(…) 全局函數,現在自動為全局隨機數生成器Int生成一個隨機值,并且頂級Seed函數已被棄用。因此,每次運行程序時會生成不同的隨機數。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

但如果在 Go 1.20 版本之前,如培訓中王克純老師演示的(Go 1.18),在調用rand.Intn()之前必須先通過 rand.Seed()設置隨機數種子,這樣才能讓每次運行生成的隨機數值不同,否則,由于隨機數種子固定,每次運行生成的隨機數也是固定的一個值,并不“隨機”。

通常,用時間戳來作為隨機數的種子。(這一點特性和C語言的rand()是類似的。)

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

//Go 1.20 之前

package main

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

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

2、讀取用戶輸入

這里直接用 fmt.Scan() 來讀取用戶的輸入。

fmt.Scan() 函數用于從標準輸入中讀取數據,并根據提供的格式字符串將輸入解析為相應的變量。這個函數會一直等待用戶輸入,直到按下回車鍵,并嘗試將輸入解析為指定的變量類型:

package main

import (
        "fmt"
)

func main() {
        var num int
        fmt.Print("請輸入一個整數: ")
        _, err := fmt.Scan(&num)
        if err != nil {
                fmt.Println("輸入錯誤:", err)
                return
        }
        fmt.Println("您輸入的整數是:", num)
}

在這個示例中,fmt.Scan(&num) 會等待用戶輸入,并將輸入解析為整數,并將其存儲到 num 變量中。如果輸入無法解析為整數,將會返回錯誤。

注意,fmt.Scan() 函數需要提供變量的地址作為參數,以便將解析后的值存儲到變量中。也可以在 fmt.Scan() 中使用格式字符串來匹配特定的輸入格式,以及處理多個變量的讀取。

通過 fmt.Scan() 完善猜數字的代碼:

maxNum := 100
secretNum := rand.Intn(maxNum)
fmt.Println("隨機數是", secretNum)

fmt.Println("請輸入你猜測的數字:>")
var guess int
_, err := fmt.Scan(&guess)
if err != nil {
    fmt.Println("輸入出錯!", err)
    return
}

3、實現邏輯判斷與循環(huán)游戲

加上 if-else 對 輸入的 guess 與 隨機數 secretNum 進行值的校驗,來得出用戶是否猜中數字;加上循環(huán),實現多次游戲。這樣,猜數字游戲的程序就完成了。

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    maxNum := 100
    secretNum := rand.Intn(maxNum)
    //fmt.Println("隨機數是", secretNum)

    for {
       fmt.Println("請輸入你猜測的數字:>")
       var guess int
       _, err := fmt.Scan(&guess)
       if err != nil {
          fmt.Println("輸入出錯!", err)
          continue    //輸入出錯不能直接退出程序,而是進入下一次輸入
       }

       if guess > secretNum {
          fmt.Println("猜大了!")
       } else if guess < secretNum {
          fmt.Println("猜小了!")
       } else {
          fmt.Println("恭喜你!猜中了!")
          break
       }
    }
}

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程


二、在線詞典

第二個案例是一個命令行詞典。效果是輸入一個單詞,命令行中會顯示這個單詞的發(fā)音和注釋。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

原理是調用第三方的API去查詢結果,并且其打印出來。

實現這個程序的關鍵在于,如何用Go語言發(fā)送HTTP請求和解析JSON。

1、抓包

以彩云小譯為例:https://fanyi.caiyunapp.com/#/

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

分析這個請求:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

2、代碼自動生成

我們要實現在Golang里發(fā)送這個請求。

但這個請求很復雜,直接用代碼來構造很麻煩,所以我們可以用另一種簡單的方式來生成請求。

右鍵瀏覽器中的請求:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

點擊 Copy as cURL(bash) 后,打開這個網址:https://curlconverter.com/go/

把剛才復制的東西粘貼進去,自動會生成響應的請求代碼:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

將這些代碼復制到Goland:

package main

import (
    "fmt"
    "io"
    "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("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
    req.Header.Set("app-name", "xy")
    req.Header.Set("cache-control", "no-cache")
    req.Header.Set("content-type", "application/json;charset=UTF-8")
    req.Header.Set("device-id", "b72e9f6cbb97432941b8adf317a17dee")
    req.Header.Set("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("pragma", "no-cache")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-site", "cross-site")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    defer resp.Body.Close()
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }
    fmt.Printf("%s\n", bodyText)
}

代碼解讀:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

這里特別解釋一下上圖中第40行的 defer 關鍵字。

在 Go 語言中,defer 是一個用于延遲函數調用執(zhí)行的關鍵字。通過使用 defer,可以在函數返回之前(無論這個函數是正常結束還是發(fā)生了異常),推遲某個函數的執(zhí)行。這在編寫代碼時可以很有用,特別是在需要確保資源釋放或清理操作的情況下。

defer 語句的語法是:

defer functionCall(arguments)

當在一個函數內部使用 defer 時,被推遲執(zhí)行的函數調用會被添加到一個棧中,而不會立即執(zhí)行。在函數執(zhí)行完畢并即將返回之前,棧中的函數調用會按照逆序執(zhí)行。因此,如果有多個 defer 語句,它們會按照后進先出(LIFO)的順序執(zhí)行,即最后一個推遲的函數調用會最先執(zhí)行,而最早的推遲的函數調用會最后執(zhí)行。

以下示例演示了有三個 defer 的情況:

package main

import "fmt"

func main() {
    defer fmt.Println("第一個defer")
    defer fmt.Println("第二個defer")
    defer fmt.Println("第三個defer")
    fmt.Println("正常的函數調用")
}

在這個示例中,盡管 fmt.Println("正常的函數調用") 在代碼中位于三個 defer 語句之前,但它會最先執(zhí)行。然后,defer 語句會按照后進先出的順序執(zhí)行,所以先執(zhí)行第三個 defer,然后是第二個 defer,最后是第一個 defer。這就是 defer 的執(zhí)行順序。

defer 的設計是為了在函數返回前執(zhí)行一些清理工作,或者確保一些資源被正確釋放。通過使用 defer,我們可以更方便地管理代碼,并確保清理操作不會被遺漏。

運行自動生成得到的代碼塊,成功輸出一大串JSON:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

說明請求成功。

但是,這片請求代碼是固定的,傳入的data也是固定的 data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`)

我們肯定希望用戶能通過一個變量進行輸入,想翻譯什么單詞,就翻譯什么單詞,而不是固定的只能輸入JSON字符串。因此,我們要用到JSON序列化。

3、生成request body

如何進行JSON序列化?在上一篇文章中提到過:https://juejin.cn/post/7265577455208955938#heading-62

只需要構造一個結構體,讓這個結構體的字段名稱和JSON的結構一一對應,然后直接調用 json.Marshal() 即可。

因此這里,我們也需要構造出一個結構體,然后將結構體序列化,通過這樣的方式,讓data中的值是可變的:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
)

//構建結構體
type DictRequest struct {
    TransType string `json:"trans_type"`
    Source    string `json:"source"`
    UserID    string `json:"user_id"`
}

func main() {
    client := &http.Client{}
    //將翻譯的類型和要翻譯的單詞傳入結構體
    request := DictRequest{TransType: "en2zh", Source: "hello"}
    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("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
    req.Header.Set("app-name", "xy")
    req.Header.Set("cache-control", "no-cache")
    req.Header.Set("content-type", "application/json;charset=UTF-8")
    req.Header.Set("device-id", "b72e9f6cbb97432941b8adf317a17dee")
    req.Header.Set("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("pragma", "no-cache")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-site", "cross-site")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    defer resp.Body.Close()
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }
    fmt.Printf("%s\n", bodyText)
}

上述代碼中,手動將要翻譯的類型(“en2zh”)和單詞(“hello”)傳入了結構體。

此時代碼的運行結果應與一開始的代碼運行結果完全相同。對請求的序列化的邏輯就完成了。

4、解析 response body

完成了請求的序列化后,我們還要進行響應的反序列化,這樣才能得到結果。這我們要解析出這一堆響應,并且獲取到其中的一些關鍵信息如"explanations"等。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

如果是python或者js,返回的會是一個字典的結構,可以直接通過 [] 或 . 去取值。但是Go是一種強類型的語言,這種方式并不是最佳實踐(雖然也可以做到)。

更常見的方式是和處理request body一樣,寫一個結構體,這個結構體的字段和返回的response是一一對應的。再把返回的 json 序列化 (json.Unmarshal())到結構體里面。

但是,我們看到,瀏覽器里返回的這個response結構非常復雜,手動一一創(chuàng)建結構體去對應顯然不是好的做法。所以我們還是用代碼生成的方式來實現。

打開這個工具網站:https://oktools.net/json2go

然后,把彩云小譯界面的 response 的json粘貼進去:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

點擊“轉換-嵌套”,就會生成一個結構體。(如果點擊“轉換-展開”,會生成獨立的多個結構體。)

將生成的結構體代碼粘貼到Goland中,將結構體改名為 DictResponse,然后修改最后處理響應的部分代碼:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
)

type DictRequest struct {
    TransType string `json:"trans_type"`
    Source    string `json:"source"`
    UserID    string `json:"user_id"`
}

//粘貼過來的響應結構體
type DictResponse struct {
    Rc   int `json:"rc"`
    Wiki struct {
    } `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"`
}

func main() {
    client := &http.Client{}
    request := DictRequest{TransType: "en2zh", Source: "hello"}
    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("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
    req.Header.Set("app-name", "xy")
    req.Header.Set("cache-control", "no-cache")
    req.Header.Set("content-type", "application/json;charset=UTF-8")
    req.Header.Set("device-id", "b72e9f6cbb97432941b8adf317a17dee")
    req.Header.Set("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("pragma", "no-cache")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-site", "cross-site")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    defer resp.Body.Close()
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }
    
    //處理響應
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
       log.Fatal(err)
    }
    //用 %#v 會以最詳細的方式來打印結構體,包括結構體的名字和字段的名字
    fmt.Println("%#v\n", dictResponse)
}

我們需要從response中篩選出我們需要的信息:如“音標”,“解釋”:

//fmt.Println("%#v\n", dictResponse)
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En,
    "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
    fmt.Println(item)
}

最終經過調整,完整的程序代碼如下:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

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

// 響應
type DictResponse struct {
    Rc   int `json:"rc"`
    Wiki struct {
    } `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"`
}

func main() {
    //檢查命令行傳入的參數個數(第一個參數是程序名稱本身,第二個參數是傳入的單詞,因此必須是兩個參數)
    if len(os.Args) != 2 {
       fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`)
       os.Exit(1)
    }
    //將第二個命令行參數也就是單詞傳給變量 word
    word := os.Args[1]
    query(word)
}

// 將程序的主要代碼封裝為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)
    }
    req.Header.Set("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
    req.Header.Set("app-name", "xy")
    req.Header.Set("cache-control", "no-cache")
    req.Header.Set("content-type", "application/json;charset=UTF-8")
    req.Header.Set("device-id", "b72e9f6cbb97432941b8adf317a17dee")
    req.Header.Set("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("pragma", "no-cache")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-site", "cross-site")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    defer resp.Body.Close()
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }

    //防御性編程
    if resp.StatusCode != 200 {
       log.Fatal("bad StatusCode:", resp.StatusCode, "body:", string(bodyText))
    }
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
       log.Fatal(err)
    }

    //fmt.Println("%#v\n", dictResponse)
    fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En,
       "US:", dictResponse.Dictionary.Prons.EnUs)
    for _, item := range dictResponse.Dictionary.Explanations {
       fmt.Println(item)
    }
}

在命令行運行:輸入要翻譯的單詞,可以調用接口實現在線翻譯

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

三、SOCKS5代理服務器

對于大家來說,一提到代理服務器,第一想到的是翻墻。不過遺憾的是, socks5 協(xié)議它雖然是代理協(xié)議,但它并不能用來翻墻,它的協(xié)議都是明文傳輸。這個協(xié)議歷史比較久遠,誕生于互聯(lián)網早期。

它的用途是,比如某些企業(yè)的內網為了確保安全性,有很嚴格的防火墻策略,但是帶來的副作用是訪問某些資源會很麻煩。socks5 相當于在防火墻開了個口子,讓授權的用戶可以通過單個端口去訪問內部的所有資源。實際上很多翻墻軟件,最終暴露的也是一個 socks5 協(xié)議的端口。

在爬蟲開發(fā)中,爬蟲的爬取過程中很容易會遇到IP訪問頻率超過限制的問題,這個時候很多人就會去網上找一些代理IP池,這些代理IP池里面的很多代理的協(xié)議也就是 socks5。

**socks5協(xié)議的原理

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

正常瀏覽器訪問一個網站,如果不經過代理服務器的話,會先和對方的網站建立 TCP 連接,然后三次握手。握手完之后發(fā)起 HTTP 請求,然后服務返回 HTTP 響應。

如果設置代理服務器之后,流程會變得復雜一些:

  • 首先是瀏覽器和 socks5 代理建立 TCP 連接,代理再和真正的服務器建立 TCP 連接。這里可以分成四個階段:

    • 握手階段
    • 認證階段
    • 請求階段
    • relay 階段
  • 第一個握手階段,瀏覽器會向 socks5 代理發(fā)送請求,包的內容包括一個協(xié)議的版本號和支持的認證的種類。

  • socks5 服務器會選中一個認證方式,返回給瀏覽器。如果返回的是 00 的話就代表不需要認證,返回其他類型的話會開始認證流程。

  • 第三個階段是請求階段,認證通過之后瀏覽器會 socks5 服務器發(fā)起請求。主要信息包括版本號,請求的類型。一般主要是 connection 請求,代表代理服務器要和某個域名或者某個 IP 地址某個端口建立 TCP 連接。代理服務器收到響應之后,會真正和后端服務器建立連接,然后返回一個響應。

  • 第四個階段是 relay 階段。此時瀏覽器會發(fā)送 正常發(fā)送請求,然后代理服務器接收到請求之后,會直接把請求轉換到真正的服務器上。然后如果真正的服務器以后返回響應的話,那么也會把請求轉發(fā)到瀏覽器這邊。實際代理服務器并不關心流量的細節(jié),可以是 HTTP流量,也可以是其它 TCP 流量。

這個就是 socks5 協(xié)議的工作原理,接下來我們嘗試去簡單地實現它。

1、TCP echo server

在實現代理服務器之前,我們先實現一個簡單的回顯服務器,以測試我們的server寫的對不對。

當運行時,此代碼將創(chuàng)建一個簡單的 TCP 服務器,監(jiān)聽在 127.0.0.1 的 1080 端口上,接受客戶端連接并將客戶端發(fā)送的數據原樣返回。以下是逐句加注釋的代碼解釋:

package main

import (
        "bufio"
        "log"
        "net"
)

func main() {
        // 在本地的 127.0.0.1:1080 地址上創(chuàng)建一個 TCP 服務器
        server, err := net.Listen("tcp", "127.0.0.1:1080")
        if err != nil {
                panic(err)
        }
        // 循環(huán)等待客戶端連接
        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()
        // 創(chuàng)建一個用于讀取客戶端數據的 bufio.Reader
        reader := bufio.NewReader(conn)
        for {
                // 從客戶端讀取一個字節(jié)
                b, err := reader.ReadByte()
                if err != nil {
                        break
                }
                // 將讀取的字節(jié)原樣發(fā)送回客戶端
                _, err = conn.Write([]byte{b})
                if err != nil {
                        break
                }
        }
}

這是一個基本的 TCP 服務器,用于接受客戶端連接并將接收到的數據返回給客戶端。每當客戶端發(fā)送一個字節(jié),服務器會將相同的字節(jié)返回。

我們通過nc命令來進行測試。首先需要下載netcat,地址及教程:https://blog.csdn.net/BoomLee/article/details/102563472

安裝配置完畢后,Goland中啟動echo-server程序(直接啟動或用命令go run echo-server.go)。啟動后,另開一個cmd窗口,在其中輸入nc命令:nc 127.0.0.1 1080

輸入什么就顯示什么,一個簡單的回顯服務器就搞定了。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

2、認證階段:auth

package main

import (
        "bufio"
        "fmt"
        "io"
        "log"
        "net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
        server, err := net.Listen("tcp", "127.0.0.1:1080") // 在 127.0.0.1 的 1080 端口上創(chuàng)建 TCP 服務器
        if err != nil {
                panic(err)
        }
        for {
                client, err := server.Accept() // 接受客戶端連接
                if err != nil {
                        log.Printf("Accept failed %v", err)
                        continue
                }
                go process(client) // 啟動一個新的 goroutine 處理客戶端連接
        }
}

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
        }
        log.Println("auth success")
}




// +----+----------+----------+
// |VER | NMETHODS | METHODS  |
// +----+----------+----------+
// | 1  |    1     | 1 to 255 |
// +----+----------+----------+
// VER: 協(xié)議版本,socks5為0x05
// NMETHODS: 支持認證的方法數量
// METHODS: 對應NMETHODS,NMETHODS的值為多少,METHODS就有多少個字節(jié)。RFC預定義了一些值的含義,內容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
        // 解析客戶端發(fā)送的認證請求
        ver, err := reader.ReadByte() // 讀取協(xié)議版本
        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)

        // 發(fā)送認證響應給客戶端
        // +----+--------+
        // |VER | METHOD |
        // +----+--------+
        // | 1  |   1    |
        // +----+--------+
        _, err = conn.Write([]byte{socks5Ver, 0x00}) // 發(fā)送無需認證的響應給客戶端
        if err != nil {
                return fmt.Errorf("write failed:%w", err)
        }
        return nil
}

這段代碼演示了一個簡單的 SOCKS5 服務器,用于處理客戶端的認證請求,并向客戶端發(fā)送響應?,F在我們用 curl 命令進行一下測試。首先還是一樣,在Goland運行項目程序。然后在另一個終端執(zhí)行curl命令:

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

此時curl 命令肯定是不成功的,因為協(xié)議還沒實現完成。但是看日志會發(fā)現, version和method 可以正常打印,說明當前我們的實現是正確的。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

3、請求階段

接下來我們開始做第三步:請求階段。

我們試圖讀取到攜帶 URL 或 IP 地址+端口的包,然后把它打印出來。auth 函數和 connect 函數類似,同樣在 process 里去調用。

再實現 connect 函數的代碼。根據請求階段的邏輯,瀏覽器會發(fā)送一個包,包里面包含如下6個字段:

  1. VER 版本號,socks5的值為0x05

  2. CMD 0x01表示CONNECT請求

  3. RSV 保留字段,值為0x00

  4. ATYP 目標地址類型,DST.ADDR的數據對應這個字段的類型。

    1. 0x01表示IPv4地址,DST.ADDR為4個字節(jié)
    2. 0x03表示域名,DST.ADDR是一個可變長度的域名
  5. DST.ADDR 一個可變長度的值

  6. DST.PORT 目標端口,固定2個字節(jié)

接下來我們要挨個去把這6個字段讀出來。

面這四個字段總共四個字節(jié),我們可以一次性把它讀出來。創(chuàng)建一個長度為4的緩沖區(qū),然后用io.ReadFull()把它整個填充滿。

//connect()

buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
    return fmt.Errorf("read header failed:%w", err)
}

這樣就能一次性讀取到前面4個字段,它們是定長的。對于每個字段,都要驗證合法性:

//connect()

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

最后還有兩個字節(jié)是 port ,我們讀取它,然后按協(xié)議規(guī)定的大端字節(jié)序轉換成數字。

由于上面的 buffer 已經不會被其他變量使用了,我們可以直接復用之前的內存,建立一個臨時的 slice ,長度是2,用于讀取,這樣的話最多會只讀兩個字節(jié)回來。 接下來我們把這個地址和端口打印出來用于調試。

//connect()

_, err = io.ReadFull(reader, buf[:2])
if err != nil {
    return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])

log.Println("dial", addr, port)

收到瀏覽器的這個請求包之后,我們需要返回一個包,這個包有很多字段,但其實大部分都不會使用:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

運行測試:雖然還沒有完全成功,但是能夠打印出IP地址和端口號了,說明實驗還是成功的。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

4、relay階段

直接用 net.Dial 建立一個 TCP 連接。建立完連接之后,同樣要加一個 defer 來關閉連接。

接下來需要建立瀏覽器和下游服務器的雙向數據轉發(fā)。標準庫的 io.Copy 可以實現一個單向數據轉發(fā)。完成雙向轉發(fā)還需啟動兩個 goroutinue。

完整代碼如下

package main

import (
    "bufio"
    "context"
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "log"
    "net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
    }
}

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

func auth(reader *bufio.Reader, conn net.Conn) (err error) {
    // +----+----------+----------+
    // |VER | NMETHODS | METHODS  |
    // +----+----------+----------+
    // | 1  |    1     | 1 to 255 |
    // +----+----------+----------+
    // VER: 協(xié)議版本,socks5為0x05
    // NMETHODS: 支持認證的方法數量
    // METHODS: 對應NMETHODS,NMETHODS的值為多少,METHODS就有多少個字節(jié)。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)
    }
    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)
    }

    // +----+--------+
    // |VER | METHOD |
    // +----+--------+
    // | 1  |   1    |
    // +----+--------+
    _, err = conn.Write([]byte{socks5Ver, 0x00})
    if err != nil {
       return fmt.Errorf("write failed:%w", err)
    }
    return nil
}

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個字節(jié)
    //   0x03表示域名,DST.ADDR是一個可變長度的域名
    // DST.ADDR 一個可變長度的值
    // DST.PORT 目標端口,固定2個字節(jié)

    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)

    // +----+-----+-------+------+----------+----------+
    // |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", 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
}

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

到此,socks5代理服務器就實現完成了。

也可以在瀏覽器中進行測試。Chrome瀏覽器只需安裝SwitchyOmega插件:

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

可以在瀏覽器里面再測試一下,在插件中新建一個情景模式, 代理服務器選 socks5,端口 1080 ,保存并啟用。此時你正常地訪問其它網站,代理服務器這邊會顯示出瀏覽器版本的域名和端口。

【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器,字節(jié)跳動青訓營/Go語言,筆記,golang,開發(fā)語言,字節(jié)跳動青訓營,并發(fā)編程

至此,Go語言入門的三個實戰(zhàn)項目就完成了。文章來源地址http://www.zghlxwxcb.cn/news/detail-643620.html

到了這里,關于【字節(jié)跳動青訓營】后端筆記整理-2 | Go實踐記錄:猜謎游戲,在線詞典,Socks5代理服務器的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Go語言實踐案例之猜謎游戲| 青訓營

    目錄 一、程序要實現效果: 二、思路分析: 三、具體代碼 四、可能存在的疑問 1.程序首先生成一個0-100的隨機整數。 2.提示玩家進行猜測。 3.每次玩家需要輸入一個數字,程序將會告訴玩家輸入的數字與生成的數字的大小關系,并且讓玩家再次猜測。 4.如果猜對了,則會告

    2024年02月09日
    瀏覽(16)
  • Go語言上手(三) | 青訓營筆記

    高質量: 各種邊界條件考慮完備 異常情況處理,穩(wěn)定性 易讀易維護 編程原則 簡單性 可讀性 生產力 例外:實現接口的方法不需要注釋 使用 gofmt (官方工具)自動格式化 代碼作用(適合公共符號) 代碼如何實現 (適合注釋實現過程) 代碼實現的原因(適合解釋代碼的外部

    2023年04月23日
    瀏覽(24)
  • 大一下暑期計劃 + 2023字節(jié)青訓營預告直播

    大一下暑期計劃 + 2023字節(jié)青訓營預告直播

    目錄 ??前言 ??后端學習方法 ??1,層次 ??2,體系 ??3,算法和數據結構 ??4,總結 ??前端學習方法 ??基礎 ??求職中如何彌補學歷差距? ??項目經歷怎么準備? ??官網鏈接 + 簡歷如何寫 ??0基礎可以跟項目嗎 ??主修Java或者C++能不能參加青訓營? ??大一下暑期計劃

    2024年02月11日
    瀏覽(19)
  • Go 語言的實戰(zhàn)案例 | 青訓營

    Go 語言的實戰(zhàn)案例 | 青訓營

    Powered by: NEFU AB-IN GO語言工程實踐課后作業(yè):實現思路、代碼以及路徑記錄 在計算機編程領域,Go 語言(也稱為 Golang)正逐漸成為越來越受歡迎的選擇。它是一門由 Google 開發(fā)的開源編程語言,以其簡潔性、高效性和強大的并發(fā)支持而聞名。Go 語言的設計目標是提供一種簡單

    2024年02月11日
    瀏覽(26)
  • Go 語言進階與依賴管理 | 青訓營

    Go 語言進階與依賴管理 | 青訓營

    Powered by: NEFU AB-IN GO語言工程實踐課后作業(yè):實現思路、代碼以及路徑記錄 Go可以充分發(fā)揮多核優(yōu)勢,高效運行 Goroutine 是Go語言中的 協(xié)程 ,一種 輕量級的線程 ,由Go語言的運行時管理,可以實現高并發(fā)的程序設計,由于輕量級的特性,goroutine可以創(chuàng)建成千上萬個,而且消耗

    2024年02月11日
    瀏覽(21)
  • 走進 Go 語言基礎語法 | 青訓營 (1)

    走進 Go 語言基礎語法 | 青訓營 (1)

    Powered by: NEFU AB-IN Go 語言入門指南:基礎語法和常用特性解析 go.mod 這個文件里記錄了當前項目里所有依賴包的 git 倉庫地址以及對應的 版本號 ,來解決了包依賴管理的問題,后續(xù)在構建編譯時,就會根據對應的版本號去拉取依賴包。 注意, 如果當前的項目是要給外部使用的

    2024年02月14日
    瀏覽(29)
  • Go 語言的實戰(zhàn)案例 SOCKS5 代理 | 青訓營

    Go 語言的實戰(zhàn)案例 SOCKS5 代理 | 青訓營

    Powered by: NEFU AB-IN GO語言工程實踐課后作業(yè):實現思路、代碼以及路徑記錄 代理是指在計算機網絡中,代理服務器充當客戶端和目標服務器之間的中介。它接收來自客戶端的請求,然后將請求轉發(fā)給目標服務器,再將目標服務器的響應返回給客戶端。 用途 : 匿名瀏覽 :Soc

    2024年02月10日
    瀏覽(47)
  • Go 語言入門指南: 環(huán)境搭建、基礎語法和常用特性解析 | 青訓營

    Go 語言入門指南: 環(huán)境搭建、基礎語法和常用特性解析 | 青訓營

    Go 是一個開源的編程語言,它能讓構造簡單、可靠且高效的軟件變得容易。 Go是從2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持開發(fā),后來還加入了Ian Lance Taylor, Russ Cox等人,并最終于2009年11月開源,在2012年早些時候發(fā)布了Go 1穩(wěn)定版本?,F在Go的開發(fā)已經是完全開放的,并且

    2024年02月14日
    瀏覽(22)
  • Go 字節(jié)跳動—從需求到上線全流程

    Go 字節(jié)跳動—從需求到上線全流程

    整個課程會帶大家先從理論出發(fā),思考為什么有流程 大家以后工作的團隊可能不一樣,那么不同的團隊也會有不同的流程,這背后的邏輯是什么 然后會帶大家按照走一遍從需求到上線的全流程,告訴大家在流程的每個階段,究竟要做什么 最后會給大家介紹一下我們實踐當中

    2024年02月07日
    瀏覽(21)
  • 網站常見安全漏洞 | 青訓營

    網站常見安全漏洞 | 青訓營

    Powered by: NEFU AB-IN 網站常見安全漏洞-網站基本組成及漏洞定義 網站常見安全漏洞-服務端漏洞介紹 網站常見安全漏洞-客戶端漏洞介紹 網站常見安全漏洞-總結及強調網絡安全法 網關 在計算機網絡中指的是連接兩個不同網絡或協(xié)議的設備或系統(tǒng)。它的作用是將傳入的數據包從

    2024年02月10日
    瀏覽(16)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包