Go如何實(shí)現(xiàn)Websocket服務(wù)以及代理
Go 實(shí)現(xiàn) Websocket服務(wù)以及代理
1. 協(xié)議說(shuō)明
WebSocket 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通信的協(xié)議。WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。Websocket 主要用在B/S架構(gòu)的應(yīng)用程序中,在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接, 并進(jìn)行雙向數(shù)據(jù)傳輸。
它的最大特點(diǎn)就是,服務(wù)器可以主動(dòng)向客戶端推送信息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息,是真正的雙向平等對(duì)話,屬于服務(wù)器推送技術(shù)的一種。
WebSocket 協(xié)議在2008年誕生,2011 年成為國(guó)際標(biāo)準(zhǔn)?,F(xiàn)在最新版本瀏覽器都已經(jīng)支持了。
WebSocket 是一種應(yīng)用層協(xié)議
WebSocket 的典型特點(diǎn):
- 基于 TCP 協(xié)議的應(yīng)用層協(xié)議,實(shí)現(xiàn)相對(duì)簡(jiǎn)單
- 單個(gè) TCP 連接上進(jìn)行全雙工通信
- 兼容 HTTP 協(xié)議,默認(rèn)端口也是 80 和 443
ws://host:port/path/querywss://host:port/path/query
- 握手階段采用 HTTP 協(xié)議,能通過(guò)各種 HTTP 代理服務(wù)器
- 數(shù)據(jù)格式比較輕量,性能開(kāi)銷(xiāo)小,通信高效
- 可以發(fā)送文本和二進(jìn)制數(shù)據(jù)
- 沒(méi)有瀏覽器的同源限制
websocket 的典型場(chǎng)景:
- 即時(shí)通信
- 協(xié)同編輯/編輯
- 實(shí)時(shí)數(shù)據(jù)流的拉取與推送
2. WebSocket 推送和瀏覽器輪詢
在 B/S 開(kāi)發(fā)領(lǐng)域,若需要瀏覽器 B 即時(shí)得到服務(wù)器的狀態(tài)更新,常使用兩個(gè)方案:
- 瀏覽器端輪詢
- 服務(wù)器端推送
瀏覽器輪詢:瀏覽器端,當(dāng)需要獲取最新數(shù)據(jù)狀態(tài)時(shí),利用腳本程序循環(huán)向服務(wù)端發(fā)送請(qǐng)求。
服務(wù)器推送,服務(wù)器端,當(dāng)狀態(tài)改變時(shí),將數(shù)據(jù)發(fā)送到瀏覽器端。
HTTP/2 版本也支持服務(wù)器端推送,但實(shí)現(xiàn)上以推送靜態(tài)資源為主,不能基于業(yè)務(wù)邏輯推送特定的消息,因此當(dāng)前的普及使用率 websocket 還是主流。
3. WebSocket 和 http
相同點(diǎn)
- 應(yīng)用層協(xié)議
- B/S 架構(gòu)中使用
- 基于 TCP 協(xié)議
- 端口默認(rèn)都是:80 和 443
不同點(diǎn)
4. WebSocket 握手過(guò)程
通過(guò) HTTP 請(qǐng)求響應(yīng),中的頭信息,完成 websocket 握手,如圖:
- 在請(qǐng)求頭中添加如下信息
# 升級(jí)為 websocket Upgrade: websocket Connection: Upgrade # 一個(gè) Base64 encode 的值,有于驗(yàn)證服務(wù)器端是否支持websocket Sec-WebSocket-Key: x4JJHMbDL22zLk1GBhXDw== # 用戶協(xié)議,可以視為不同業(yè)務(wù)邏輯的頻道 Sec-WebSocket-Protocol: chat # 協(xié)議版本,13是當(dāng)前通用版本,幾乎不需要更改 Sec-WebSocket-Version: 13
基于以上請(qǐng)求頭,服務(wù)器端,就知道需要將協(xié)議升級(jí)為 websocket 協(xié)議,并提供一些驗(yàn)證信息。
- 服務(wù)端的響應(yīng)頭
HTTP/1.1 101 Switching Protocols # 協(xié)議升級(jí) Upgrade: websocket # 連接狀態(tài) Connection: Upgrade # WebSocket服務(wù)端根據(jù)Sec-WebSocket-Key生成 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # WebSocket協(xié)議用戶協(xié)議 Sec-WebSocket-Protocol: chat
基于以上響應(yīng)頭,瀏覽器端就知道服務(wù)器端升級(jí)成功,并通過(guò)了驗(yàn)證。
至此,B/S 端可以基于該連接,完成 websocket 雙向通信了。
websocket 只能發(fā)送 GET 請(qǐng)求
5. WebSocket 狀態(tài)碼和消息類(lèi)型
5.1 狀態(tài)碼
5.2 消息類(lèi)型
TextMessage 和 BinaryMessage 分別表示發(fā)送文本消息和二級(jí)制消息
CloseMessage 關(guān)閉幀,接收方收到這個(gè)消息就關(guān)閉連接
PingMessage 和 PongMessage : 是保持心跳的幀
- 發(fā)送方 -> 接收方是 PingMessage
- 接收方 -> 發(fā)送方是 PongMessage
由服務(wù)器發(fā) ping 給瀏覽器,瀏覽器返回 pong 消息
6. WebSocket 服務(wù)器實(shí)現(xiàn)
使用 github.com/gorilla/websocket 這個(gè)庫(kù)函數(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 服務(wù)器正在運(yùn)行。按Ctrl+C退出") select {} } func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) { // 初始化 Upgrader upgrader := websocket.Upgrader{} // 使用默認(rèn)的選項(xiàng) // 第三個(gè)參數(shù)是響應(yīng)頭,默認(rèn)會(huì)初始化 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 { // 消息類(lèi)型:文本消息和二進(jìn)制消息 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 測(cè)試 websocket 是否能連接并且發(fā)送消息
消息發(fā)送成功,同時(shí)也接收到來(lái)服務(wù)端的消息
消息接收成功
7. WebSocket 代理實(shí)現(xiàn)
package websocket import ( "log" "net/http" "net/http/httputil" "net/url" ) var ( // 代理服務(wù)器地址 proxyServer = "127.0.0.1:8082" // 真實(shí)websocket服務(wù)器地址 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 代理啟動(dòng), 按CTRL+C退出") http.ListenAndServe(proxyServer, proxy) }
8. WebSocket 服務(wù)端主動(dòng)推送功能的實(shí)現(xiàn)
websocket 服務(wù)器每隔 3 秒會(huì)主動(dòng)向服務(wù)器推送消息"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 服務(wù)器正在運(yùn)行。按Ctrl+C退出") select {} } func WebSocketUpgrade(resp http.ResponseWriter, req *http.Request) { // 初始化 Upgrader upgrader := websocket.Upgrader{} // 使用默認(rèn)的選項(xiàng) // 第三個(gè)參數(shù)是響應(yīng)頭,默認(rèn)會(huì)初始化 conn, err := upgrader.Upgrade(resp, req, nil) if err != nil { log.Println(err) return } defer conn.Close() // 主動(dòng)向服務(wù)端推送消息 go PushMessage(conn) // 讀取客戶端的發(fā)送額消息,并返回 go ReadMessage(conn) select {} } // websocket 服務(wù)器主動(dòng)服務(wù)器推送消息 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 { // 消息類(lèi)型:文本消息和二進(jìn)制消息 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 } } }
每隔三秒可以看到服務(wù)推送過(guò)來(lái)的消息
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Go 語(yǔ)言 JSON 標(biāo)準(zhǔn)庫(kù)的使用
今天通過(guò)本文給大家介紹Go 語(yǔ)言 JSON 標(biāo)準(zhǔn)庫(kù)的使用小結(jié),包括序列化和反序列化的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2021-10-10Go?常見(jiàn)設(shè)計(jì)模式之單例模式詳解
單例模式是設(shè)計(jì)模式中最簡(jiǎn)單的一種模式,單例模式能夠確保無(wú)論對(duì)象被實(shí)例化多少次,全局都只有一個(gè)實(shí)例存在,在Go?語(yǔ)言有多種方式可以實(shí)現(xiàn)單例模式,所以我們今天就來(lái)一起學(xué)習(xí)下吧2023-07-07golang?基于?mysql?簡(jiǎn)單實(shí)現(xiàn)分布式讀寫(xiě)鎖
這篇文章主要介紹了golang?基于mysql簡(jiǎn)單實(shí)現(xiàn)分布式讀寫(xiě)鎖,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09Go-RESTful實(shí)現(xiàn)下載功能思路詳解
這篇文章主要介紹了Go-RESTful實(shí)現(xiàn)下載功能,文件下載包括文件系統(tǒng)IO和網(wǎng)絡(luò)IO,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10go中Excelize處理excel表實(shí)現(xiàn)帶數(shù)據(jù)校驗(yàn)的文件導(dǎo)出
本文主要介紹了go中Excelize處理excel表實(shí)現(xiàn)帶數(shù)據(jù)校驗(yàn)的文件導(dǎo)出,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06