p2p

我不太確定這是 p2p 還是穿隧,但反正功能就是可以讓兩個在不同內網的機器可以直連,不需要透過中央伺服器轉發。先上原理圖,然後再來一步一步看程式碼 Server 首先,先來看伺服器端,這邊做的事很簡單,這邊開了一個 UDP listener,然後開一個陣列存兩個 client 的 UDPAddr listener, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4zero, Port: port, }) if err != nil { log.Println(err) } peers := make([]net.UDPAddr, 0, 2) 然後在 for 迴圈中傾聽連線,這邊收到的 data 會是一串不重要的訊息,但是 ReadFromUDP 回傳的 remoteAddr 就很重要,要存起來 for { n, remoteAddr, err := listener.ReadFromUDP(data) if err != nil { log.Printf("err during read: %s\n", err) } peers = append(peers, *remoteAddr) 到這邊,可能就結束了,但是如果這時候是第二個連線(len(peers) == 2),就可以開始建立 p2p 連線,將 A 的 addr 傳給 B,將 B 的 addr 傳給 A。然後若干秒後結束伺服器,伺服器端的程式碼就到這裡結束了...

2022-August-16 · 2 分鐘 · simbafs

用 Github Action 編譯並發 Release

如果自己寫的小工具的 GitHub 頁面右邊 Release 那欄有個什麼東西,一定很酷對吧!如果裡面已經提供了不同作業系統編譯好的程式,一定更酷! 想要建立 Release,你可以在新版本發布時自己手動 crose compile 再手動設定 Release,這個方法可行,但是聽起來全手動就很 low,我們要用一個全自動的方式發 Release! Github Actioin 既然我們程式碼都託管在 GitHub 了,直接用 Gtithub Action 是很合理的吧! 觸發條件 因為我們要做的是發布版本,不是每個 commit 都要觸發,因此觸發條件就設成 on: push: tags: - "v[0-9]+.[0-9]+.[0-9]+" 意思是只有像是 v0.1.13 這樣的標籤會觸發,也就是你建立新版本時。 編譯 在嘗試各種套件之後,我覺得 goreleaser-action 是我用起來最舒服的,不用太多設定,就直接都編譯好了(詳細設定可以去 goreleaser 的網站看 ) 在 GitHub Action 中設定就下面幾行,第一個步驟是安裝 go,再來就是編譯了。 - name: Set up Go uses: actions/[email protected] with: go-version: 1.17.x - name: Run GoReleaser uses: goreleaser/[email protected] with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets....

2022-March-27 · 2 分鐘 · simbafs

Golang Plguin

Go 動態載入程式 Go 是一個編譯式的語言,也就是說他不像 JS 那樣可以動態執行程式碼。像是 Hexo 和 Hugo,前者因為是 JS 寫的,因此支援非常豐富的外掛,但後者因為是 Go 寫的,因此在不使用其他直譯式程式語言的情況下,很難製作外掛。 另一個 Go 寫的軟體 ponzu 則是在加入一段程式碼後,重新編譯自己。這麼做解決了外掛的問題,而且又不會失去 Go 的快速,但是就必須保留整個主程式的原始碼,而且也不那麼的「動態」 ponzu 這個軟體兩年沒人維護了,很多東西都怪怪的,超級難編譯 Plugin in Go 在 1.8 版的時候,Go 推出了 Plguin 套件,可以將外掛和主程式分開編譯,如果外掛有更動,不需要重新編譯主程式;主程式也可以動態載入外掛。 外掛 如果要將一個 package 編譯成外掛,首先他的 package 必須是 main,但是裡面的函式 main、init 都不會被執行,只有大寫開頭的變數、型態、函式會被暴露給主程式。 編譯外掛時,需要加上 -buildmode=plguin,這樣 go build 就會將原始碼編譯成 .so(share object)檔,這樣就可以被主程式呼叫。 主程式 主程式要載入外掛前,需要引用 plugin 套件 import "plugin" 用 func Open(path string) (*Plugin, error) 可以載入一個編譯過的外掛,如果重複呼叫這個函式,除了第二次外都會回傳第一次載入的結果,也就是說假如你在很多個 gorutine 中載入同一個外掛,go 會保證他是「安全」的。注意看,path 是個字串,因此你可以動態產生外掛的路徑,不需要寫死。 載入完了之後,用 func (p *Plugin) Lookup(symName string) (Symbol, error) 可以取得外掛暴露出來的變數、函式,因為 symName 是字串,因此這裡也可以動態選擇要的變數。現在你有一個型態是 Symbol 的變數了,其實這個 Symbol 就是 interface{} 所以不管要做什麼事,你都要先用 symbol....

2022-February-21 · 1 分鐘 · simbafs

ExecCmd

Golang 執行外部命令 package main import ( "fmt" "os/exec" ) func checkErr(err error) { if err != nil { panic(err) } } func main() { pw := exec.Command("wc") stdin, err := pw.StdinPipe() checkErr(err) fmt.Fprintln(stdin, "fjasdfkjsad\njdfaksdfjksdfjkasdfj\ndjfkajsdk") output, err := pw.Output() checkErr(err) fmt.Println(string(output)) }

2021-October-19 · 1 分鐘 · simbafs

Aconf

aconf 是個可以直接幫你解決所有「設定」問題的套件,設定可以有預設、從命令列參數、環境變數和設定檔,設定檔還有官方支援四種格式,dotEnv、HCL、toml、yaml 和 json。 而且設定檔還可以有不只一個,他可以把多個設定檔合成,相當方便 基本使用 定義 struct 首先我們需要一個 struct 來定義我們的設定 type Config struct { Addr string `default:":3000"` Title string `default:"Aconf Testing"` SysAdmin []string `default:"simbafs,peter"` } 載入設定 再來,我們可以設定要從哪些來源載入設定值 loader := aconfig.LoaderFor(&config, aconfig.Config{ // 這四個預設都是關閉的,如果你想關閉任何一個隨時都可以關閉他 // SkipDefaults: false, // SkipFiles: false, // SkipEnv: false, // SkipFlags: false, EnvPrefix: "APP", FlagPrefix: "app", Files: []string{"~/.config/app.toml", "app.toml"}, FileDecoders: map[string]aconfig.FileDecoder{ ".toml": aconfigtoml.New(), }, }) if err := loader.Load(); err != nil { panic(err) } 這樣你就可以用檔案、環境變數和命令列參數設定了。...

2021-September-24 · 1 分鐘 · simbafs

Golang Parse All Files In Directory Into Templates

在用 gin 寫伺服器的時候,我發現模板並不會被 go build 打包進執行檔裡面,所以在執行的時候就找不到檔案,當然這個問題可以用字串的形式直接把模板放進 golang 原始碼裡面,但是這樣程式碼一複雜就會不好用,這時候就可以用 golang 的 embed 函式庫把檔案「嵌入」到原始碼裡面。但是問題又來了,嵌入了之後要怎麼把字串變成模板物件呢? Embed embed 套件是 1.16 新出的功能,所以如果想用的話記得要更新 go 到 1.16 以上 embed 嵌入檔案的方式是透過特殊格式的註解宣告,直接看官方範例: package main import ( "embed" ) //go:embed hello.txt var s string //go:embed hello.txt var b []byte //go:embed hello.txt var f embed.FS func main() { print(s) print(string(b)) data, _ := f.ReadFile("hello.txt") print(string(data)) } 可以看到,embed 可以把檔案讀成三種格式 string、[]byte、embed.FS,前兩者只能讀「一個」檔案,如果你只用這兩個的話引入時要用 _ "embed"。embed.FS 除了可以嵌入多個檔案之外,因為實做了 fs.FS 所以可以使用當作一個檔案系統操作。 Tmeplate No Recursive 最簡單的方法,你可以直接使用 // go:embed view/* var f embed....

2021-July-30 · 2 分鐘 · simbafs