Go如何實現(xiàn)Websocket服務以及代理
Go 實現(xiàn) Websocket服務以及代理
1. 協(xié)議說明
WebSocket 是一種在單個 TCP 連接上進行全雙工通信的協(xié)議。WebSocket 使得客戶端和服務器之間的數(shù)據(jù)交換變得更加簡單,允許服務端主動向客戶端推送數(shù)據(jù)。Websocket 主要用在B/S架構的應用程序中,在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接, 并進行雙向數(shù)據(jù)傳輸。
它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發(fā)送信息,是真正的雙向平等對話,屬于服務器推送技術的一種。
WebSocket 協(xié)議在2008年誕生,2011 年成為國際標準。現(xiàn)在最新版本瀏覽器都已經支持了。
WebSocket 是一種應用層協(xié)議
WebSocket 的典型特點:
- 基于 TCP 協(xié)議的應用層協(xié)議,實現(xiàn)相對簡單
- 單個 TCP 連接上進行全雙工通信
- 兼容 HTTP 協(xié)議,默認端口也是 80 和 443
ws://host:port/path/querywss://host:port/path/query
- 握手階段采用 HTTP 協(xié)議,能通過各種 HTTP 代理服務器
- 數(shù)據(jù)格式比較輕量,性能開銷小,通信高效
- 可以發(fā)送文本和二進制數(shù)據(jù)
- 沒有瀏覽器的同源限制
websocket 的典型場景:
- 即時通信
- 協(xié)同編輯/編輯
- 實時數(shù)據(jù)流的拉取與推送
2. WebSocket 推送和瀏覽器輪詢
在 B/S 開發(fā)領域,若需要瀏覽器 B 即時得到服務器的狀態(tài)更新,常使用兩個方案:
- 瀏覽器端輪詢
- 服務器端推送
瀏覽器輪詢:瀏覽器端,當需要獲取最新數(shù)據(jù)狀態(tài)時,利用腳本程序循環(huán)向服務端發(fā)送請求。
服務器推送,服務器端,當狀態(tài)改變時,將數(shù)據(jù)發(fā)送到瀏覽器端。
HTTP/2 版本也支持服務器端推送,但實現(xiàn)上以推送靜態(tài)資源為主,不能基于業(yè)務邏輯推送特定的消息,因此當前的普及使用率 websocket 還是主流。
3. WebSocket 和 http
相同點
- 應用層協(xié)議
- B/S 架構中使用
- 基于 TCP 協(xié)議
- 端口默認都是:80 和 443
不同點
4. WebSocket 握手過程
通過 HTTP 請求響應,中的頭信息,完成 websocket 握手,如圖:
- 在請求頭中添加如下信息
# 升級為 websocket Upgrade: websocket Connection: Upgrade # 一個 Base64 encode 的值,有于驗證服務器端是否支持websocket Sec-WebSocket-Key: x4JJHMbDL22zLk1GBhXDw== # 用戶協(xié)議,可以視為不同業(yè)務邏輯的頻道 Sec-WebSocket-Protocol: chat # 協(xié)議版本,13是當前通用版本,幾乎不需要更改 Sec-WebSocket-Version: 13
基于以上請求頭,服務器端,就知道需要將協(xié)議升級為 websocket 協(xié)議,并提供一些驗證信息。
- 服務端的響應頭
HTTP/1.1 101 Switching Protocols # 協(xié)議升級 Upgrade: websocket # 連接狀態(tài) Connection: Upgrade # WebSocket服務端根據(jù)Sec-WebSocket-Key生成 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # WebSocket協(xié)議用戶協(xié)議 Sec-WebSocket-Protocol: chat
基于以上響應頭,瀏覽器端就知道服務器端升級成功,并通過了驗證。
至此,B/S 端可以基于該連接,完成 websocket 雙向通信了。
websocket 只能發(fā)送 GET 請求
5. WebSocket 狀態(tài)碼和消息類型
5.1 狀態(tài)碼
5.2 消息類型
TextMessage 和 BinaryMessage 分別表示發(fā)送文本消息和二級制消息
CloseMessage 關閉幀,接收方收到這個消息就關閉連接
PingMessage 和 PongMessage : 是保持心跳的幀
- 發(fā)送方 -> 接收方是 PingMessage
- 接收方 -> 發(fā)送方是 PongMessage
由服務器發(fā) ping 給瀏覽器,瀏覽器返回 pong 消息
6. WebSocket 服務器實現(xiàn)
使用 github.com/gorilla/websocket 這個庫函數(shù)
func WebSocketServer() { addr := "localhost:8002" http.HandleFunc("/wshandler", WebSocketUpgrade) log.Println("Starting websocket server at " + addr) go func() { err := http.ListenAndServe(addr, nil) if err != nil { log.Fatal(err) } }() log.Println("WebSocket 服務器正在運行。按Ctrl+C退出") select {} } func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) { // 初始化 Upgrader upgrader := websocket.Upgrader{} // 使用默認的選項 // 第三個參數(shù)是響應頭,默認會初始化 conn, err := upgrader.Upgrade(resp, req, nil) if err != nil { log.Println(err) return } defer conn.Close() // 讀取客戶端的發(fā)送額消息,并返回 go ReadMessage(conn) select {} } // 讀取客戶端發(fā)送的消息,并返回 func ReadMessage(conn *websocket.Conn) { for { // 消息類型:文本消息和二進制消息 messageType, msg, err := conn.ReadMessage() if err != nil { log.Println(err) return } fmt.Println("receive msg:", string(msg)) err = conn.WriteMessage(messageType, msg) if err != nil { log.Println("write error:", err) return } } }
使用 Apifox 測試 websocket 是否能連接并且發(fā)送消息
消息發(fā)送成功,同時也接收到來服務端的消息
消息接收成功
7. WebSocket 代理實現(xiàn)
package websocket import ( "log" "net/http" "net/http/httputil" "net/url" ) var ( // 代理服務器地址 proxyServer = "127.0.0.1:8082" // 真實websocket服務器地址 websocketServer = "http://127.0.0.1:8002" ) func WebSocketProxy() { url, err := url.Parse(websocketServer) if err != nil { log.Println(err) } proxy := httputil.NewSingleHostReverseProxy(url) log.Println("WebSocket 代理啟動, 按CTRL+C退出") http.ListenAndServe(proxyServer, proxy) }
8. WebSocket 服務端主動推送功能的實現(xiàn)
websocket 服務器每隔 3 秒會主動向服務器推送消息"Heart Beat" func WebSocketServer() { addr := "localhost:8002" http.HandleFunc("/wshandler", WebSocketUpgrade) log.Println("Starting websocket server at " + addr) go func() { err := http.ListenAndServe(addr, nil) if err != nil { log.Fatal(err) } }() log.Println("WebSocket 服務器正在運行。按Ctrl+C退出") select {} } func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) { // 初始化 Upgrader upgrader := websocket.Upgrader{} // 使用默認的選項 // 第三個參數(shù)是響應頭,默認會初始化 conn, err := upgrader.Upgrade(resp, req, nil) if err != nil { log.Println(err) return } defer conn.Close() // 主動向服務端推送消息 go PushMessage(conn) // 讀取客戶端的發(fā)送額消息,并返回 go ReadMessage(conn) select {} } // websocket 服務器主動服務器推送消息 func PushMessage(conn *websocket.Conn) { for { err := conn.WriteMessage(websocket.TextMessage, []byte("heart beat")) if err != nil { log.Println(err) return } time.Sleep(time.Second * 3) } } // 讀取客戶端發(fā)送的消息,并返回 func ReadMessage(conn *websocket.Conn) { for { // 消息類型:文本消息和二進制消息 messageType, msg, err := conn.ReadMessage() if err != nil { log.Println(err) return } fmt.Println("receive msg:", string(msg)) err = conn.WriteMessage(messageType, msg) if err != nil { log.Println("write error:", err) return } } }
每隔三秒可以看到服務推送過來的消息
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
golang?基于?mysql?簡單實現(xiàn)分布式讀寫鎖
這篇文章主要介紹了golang?基于mysql簡單實現(xiàn)分布式讀寫鎖,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09go中Excelize處理excel表實現(xiàn)帶數(shù)據(jù)校驗的文件導出
本文主要介紹了go中Excelize處理excel表實現(xiàn)帶數(shù)據(jù)校驗的文件導出,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06