gotalk
專一於進程間的通訊,致力於簡化通訊協議和流程。同時它:javascript
gotalk.js
,方便開發基於 Web 網頁的客戶端程序;那麼,讓咱們來玩一下吧~html
本文代碼使用 Go Modules。java
建立目錄並初始化:git
$ mkdir gotalk && cd gotalk $ go mod init github.com/darjun/go-daily-lib/gotalk
安裝gotalk
庫:github
$ go get -u github.com/rsms/gotalk
接下來讓咱們來編寫一個簡單的 echo 程序,服務端直接返回收到的客戶端信息,不作任何處理。首先是服務端:golang
// get-started/server/server.go package main import ( "log" "github.com/rsms/gotalk" ) func main() { gotalk.Handle("echo", func(in string) (string, error) { return in, nil }) if err := gotalk.Serve("tcp", ":8080", nil); err != nil { log.Fatal(err) } }
經過gotalk.Handle()
註冊消息處理,它接受兩個參數。第一個參數爲消息名,字符串類型,保證惟一且可辨識便可。第二個參數爲處理函數,收到對應名稱的消息,調用該函數處理。處理函數接受一個參數,返回兩個值。正常處理完成經過第一個返回值傳遞處理結果,出錯時經過第二個返回值表示錯誤類型。瀏覽器
這裏的處理器函數比較簡單,接受一個字符串參數,直接原樣返回。安全
而後,調用gotalk.Serve()
啓動服務器,監聽端口。它接受 3 個參數,協議類型、監聽地址、處理器對象。此處咱們使用 TCP 協議,監聽本地8080
端口,使用默認處理器對象,傳入nil
便可。服務器
服務器內部一直循環處理請求。微信
而後是客戶端:
func main() { s, err := gotalk.Connect("tcp", ":8080") if err != nil { log.Fatal(err) } for i := 0; i < 5; i++ { var echo string if err := s.Request("echo", "hello", &echo); err != nil { log.Fatal(err) } fmt.Println(echo) } s.Close() }
客戶端首先調用gotalk.Connect()
鏈接服務器,它接受兩個參數:協議和地址(IP + 端口)。咱們使用與服務器一致的協議和地址便可。鏈接成功會返回一個鏈接對象。調用鏈接對象的Request()
方法,便可向服務器發送消息。Request()
方法接受 3 個參數。第一個參數爲消息名,這對應於服務器註冊的消息名,請求一個不存在的消息名會返回錯誤。第二個參數是傳給服務器的參數,有且只能有一個參數,對應處理器函數的入參。第三個參數爲返回值的指針,用於接受服務器返回的結果。
若是請求失敗,返回錯誤err
。使用完成以後不要忘記關閉鏈接對象。
先運行服務器:
$ go run server.go
在開啓一個命令行,運行客戶端:
$ go run client.go hello hello hello hello hello
實際上若是瞭解標準庫net/http
,你應該就會發現,使用gotalk
的服務端代碼與使用net/http
編寫 Web 服務器很是類似。都很是簡單,清晰:
// get-started/http/main.go package main import ( "fmt" "log" "net/http" ) func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world") } func main() { http.HandleFunc("/", index) if err := http.ListenAndServe(":8888", nil); err != nil { log.Fatal(err) } }
運行:
$ go run main.go
使用 curl 驗證:
$ curl localhost:8888 hello world
除了 TCP,gotalk
還支持基於 WebSocket 協議的通訊。下面咱們使用 WebSocket 重寫上面的服務端程序,而後編寫一個簡單 Web 頁面與之通訊。
服務端:
func main() { gotalk.Handle("echo", func(in string) (string, error) { return in, nil }) http.Handle("/gotalk/", gotalk.WebSocketHandler()) http.Handle("/", http.FileServer(http.Dir("."))) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
gotalk
消息處理函數的註冊仍是與前面的同樣。不一樣的是這裏將 HTTP 路徑/gotalk/
的請求交由gotalk.WebSocketHandler()
處理,這個處理器負責 WebSocket 請求。同時,在當前工做目錄開啓一個文件服務器,掛載到 HTTP 路徑/
上。文件服務器是爲了客戶端方便地請求index.html
頁面。最後調用http.ListenAndServe()
開啓 Web 服務器,監聽端口 8080。
而後是客戶端,gotalk
爲了方便 Web 程序的編寫,將 WebSocket 通訊細節封裝在一個 JavaScript 文件gotalk.js
中。能夠直接從倉庫中的 js 目錄下獲取使用。接着咱們編寫頁面index.html
,引入gotalk.js
:
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"> <script type="text/javascript" src="gotalk/gotalk.js"></script> </head> <body> <input id="txt"> <button id="snd">send</button><br> <script> let c = gotalk.connection() .on('open', () => log(`connection opened`)) .on('close', reason => log(`connection closed (reason: ${reason})`)) let btn = document.querySelector("#snd") let txt = document.querySelector("#txt") btn.onclick = async () => { let content = txt.value if (content.length === 0) { alert("no message") return } let res = await c.requestp('echo', content) log(`reply: ${JSON.stringify(res, null, 2)}`) return false } function log(message) { document.body.appendChild(document.createTextNode(message)) document.body.appendChild(document.createElement("br")) } </script> </body> </html>
首先調用gotalk.connection()
鏈接服務端,返回一個鏈接對象。調用此對象的on()
方法,分別註冊鏈接創建和斷開的回調。而後給按鈕添加回調,每次點擊將輸入框中的內容發送給服務端。調用鏈接對象的requestp()
方法發送請求,第一個參數爲消息名,對應在服務端使用gotalk.Handle()
註冊的名字。第二個即爲處理參數,會一併發送給服務端。這裏使用 Promise 處理異步請求和響應,爲了編寫方便和易於理解使用async-await
同步的寫法。響應的內容直接顯示在頁面上:
注意,gotalk.js
文件須要放在服務器運行目錄的gotalk
目錄下。
gotalk
採用基於 ASCII 的協議格式,設計爲方便人類閱讀且靈活的。每條傳輸的消息都分爲幾個部分:類型標識、請求ID、操做、消息內容。
gotalk
能夠同時發送任意個請求並接收以前請求的響應。因此須要有一個 ID 來標識接收到的響應對應以前發送的哪條請求。看一個官方請求的示例:
+------------------ SingleRequest | +---------------- requestID "0001" | | +--------- operation "echo" (text3Size 4, text3Value "echo") | | | +- payloadSize 25 | | | | r0001004echo00000019{"message":"Hello World"}
r
:表示這是一個單條請求。0001
:請求 ID 爲 1,這裏採用十六進制編碼。004echo
:這部分表示操做爲"echo",在實際字符串內容前須要指定長度,不然接收方不知道內容在哪裏結束。004
指示"echo"長度爲 4,一樣採用十六進制編碼。00000019{"message":"Hello World"}
:這部分是消息的內容。一樣須要指定長度,十六進制00000019
表示長度爲 25。詳細格式能夠查看官方文檔。
使用這種可閱讀的格式給問題排查帶來了極大的便利。可是在實際使用中,可能須要考慮安全和隱私的問題。
examples
內置一個基於 WebSocket 的聊天室示例程序。特性以下:
animals/jokes/golang
;運行:
$ go run server.go
打開瀏覽器,輸入"localhost:1235",顯示以下:
接下來就能夠建立房間,在房間聊天了。
整個實現的有幾個要點:
其一,gotalk.WebSocketHandler()
建立的 WebSocket 處理器能夠設置鏈接回調:
gh := gotalk.WebSocketHandler() gh.OnConnect = onConnect
在回調中設置隨機用戶名,並將當前鏈接的gotalk.Sock
存儲下來,方便消息廣播:
func onConnect(s *gotalk.WebSocket) { socksmu.Lock() defer socksmu.Unlock() socks[s] = 1 username := randomName() s.UserData = username }
其二,gotalk
設置處理器函數能夠有兩個參數,第一個表示當前鏈接,第二個纔是實際接收到的消息參數。
其三,enableGracefulShutdown()
函數實現了 Web 服務器的優雅關閉,很是值得學習。接收到SIGINT
信號,先關閉全部的鏈接,再退出程序。注意監聽信號和運行 HTTP 服務器並非同一個 goroutine,看它們是如何協做的:
func enableGracefulShutdown(server *http.Server, timeout time.Duration) chan struct{} { server.RegisterOnShutdown(func() { // close all connected sockets fmt.Printf("graceful shutdown: closing sockets\n") socksmu.RLock() defer socksmu.RUnlock() for s := range socks { s.CloseHandler = nil // avoid deadlock on socksmu (also not needed) s.Close() } }) done := make(chan struct{}) quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT) go func() { <-quit // wait for signal fmt.Printf("graceful shutdown initiated\n") ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() server.SetKeepAlivesEnabled(false) if err := server.Shutdown(ctx); err != nil { fmt.Printf("server.Shutdown error: %s\n", err) } fmt.Printf("graceful shutdown complete\n") close(done) }() return done }
接收到SIGINT
信號後done
通道關閉,server.ListenAndServe()
返回http.ErrServerClosed
錯誤,退出循環:
done := enableGracefulShutdown(server, 5*time.Second) // Start server fmt.Printf("Listening on http://%s/\n", server.Addr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { panic(err) } <- done
整個聊天室功能比較簡單,代碼也比較短,建議深刻理解。在此基礎之上作擴展也比較簡單。
gotalk
實現了一個簡單、易用的通訊庫。而且提供了 JavaScript 文件gotalk.js
,方便 Web 程序的開發。協議格式清晰,易調試。內置豐富的示例。整個庫的代碼也不長,建議深刻了解。
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~