使用 Go 和 ReactJS 構(gòu)建聊天系統(tǒng)(二):gorilla/websocket 包提供的 WebSockets
本節(jié)完整代碼:GitHub[1]
本文是使用 ReactJS 和 Go 來構(gòu)建聊天應(yīng)用程序的系列文章的第 2 部分。你可以在這里找到第 1 部分 - 初始化設(shè)置[2]
現(xiàn)在我們已經(jīng)建立好了基本的前端和后端,現(xiàn)在需要來完善一些功能了。
在本節(jié)中,我們將實(shí)現(xiàn)一個基于 WebSocket 的服務(wù)器。
在該系列教程結(jié)束時,我們將有一個可以于后端雙向通信的前端應(yīng)用程序。
服務(wù)
我們可以使用 github.com/gorilla/websocket 包來設(shè)置 WebSocket 服務(wù)以及處理 WebSocket 連接的讀寫操作。
這需要在我們的 backend/ 目錄中運(yùn)行此命令來安裝它:
$?go?get?github.com/gorilla/websocket
一旦我們成功安裝了這個包,我們就可以開始構(gòu)建我們的 Web 服務(wù)了。我們首先創(chuàng)建一個非常簡單的 net/http 服務(wù):
package?main
import?(
?"fmt"
?"net/http"
)
func?setupRoutes()?{
?http.HandleFunc("/",?func(w?http.ResponseWriter,?r?*http.Request)?{
??fmt.Fprintf(w,?"Simple?Server")
?})
}
func?main()?{
?setupRoutes()
?http.ListenAndServe(":8080",?nil)
}
可以通過調(diào)用 go run main.go 來啟動服務(wù),該服務(wù)將監(jiān)聽 http://localhost:8080[3] 。如果用瀏覽器打開此連接,可以看到輸出 Simple Server。
WebSocket 協(xié)議
在開始寫代碼之前,我們需要了解一下理論。
WebSockets 可以通過 TCP 連接進(jìn)行雙工通信。這讓我們可以通過單個 TCP 套接字來發(fā)送和監(jiān)聽消息,從而避免通過輪詢 Web 服務(wù)器去通信,每次輪詢操作都會執(zhí)行 TCP 握手過程。
WebSockets 大大減少了應(yīng)用程序所需的網(wǎng)絡(luò)帶寬,并且使得我們在單個服務(wù)器實(shí)例上維護(hù)大量客戶端。
連接
WebSockets 肯定有一些值得考慮的缺點(diǎn)。比如一旦引入狀態(tài),在跨多個實(shí)例擴(kuò)展應(yīng)用程序的時候就變得更加復(fù)雜。
在這種場景下需要考慮更多的情況,例如將狀態(tài)存儲在消息代理中,或者存儲在數(shù)據(jù)庫/內(nèi)存緩存中。
實(shí)現(xiàn)
在實(shí)現(xiàn) WebSocket 服務(wù)時,我們需要創(chuàng)建一個端點(diǎn),然后將該端點(diǎn)的連接從標(biāo)準(zhǔn)的 HTTP 升級到 WebSocket。
值得慶幸的是,gorilla/websocket 包提供了我們所需的功能,可以輕松地將 HTTP 連接升級到 WebSocket 連接。
注意 - 你可以查看官方 WebSocket 協(xié)議的更多信息:RFC-6455[4]
創(chuàng)建 WebSocket 服務(wù)端
現(xiàn)在已經(jīng)了解了理論,來看看如何去實(shí)踐。我們創(chuàng)建一個新的端點(diǎn) /ws,我們將從標(biāo)準(zhǔn)的 http 端點(diǎn)轉(zhuǎn)換為 ws 端點(diǎn)。
此端點(diǎn)將執(zhí)行 3 項(xiàng)操作,它將檢查傳入的 HTTP 請求,然后返回 true 以打開我們的端點(diǎn)到客戶端。然后,我們使用定義的 upgrader 升級為 WebSocket 連接。
最后,我們將開始監(jiān)聽傳入的消息,然后將它們打印出來并將它們傳回相同的連接。這可以讓我們驗(yàn)證前端連接并從新創(chuàng)建的 WebSocket 端點(diǎn)來發(fā)送/接收消息:
package?main
import?(
?"fmt"
?"log"
?"net/http"
?"github.com/gorilla/websocket"
)
//?我們需要定義一個?Upgrader
//?它需要定義?ReadBufferSize?和?WriteBufferSize
var?upgrader?=?websocket.Upgrader{
?ReadBufferSize:??1024,
?WriteBufferSize:?1024,
?//?可以用來檢查連接的來源
?//?這將允許從我們的 React 服務(wù)向這里發(fā)出請求。
?//?現(xiàn)在,我們可以不需要檢查并運(yùn)行任何連接
?CheckOrigin:?func(r?*http.Request)?bool?{?return?true?},
}
//?定義一個?reader?用來監(jiān)聽往?WS?發(fā)送的新消息
func?reader(conn?*websocket.Conn)?{
?for?{
??//?讀消息
??messageType,?p,?err?:=?conn.ReadMessage()
??if?err?!=?nil?{
???log.Println(err)
???return
??}
??//?打印消息
??fmt.Println(string(p))
??if?err?:=?conn.WriteMessage(messageType,?p);?err?!=?nil?{
???log.Println(err)
???return
??}
?}
}
//?定義?WebSocket?服務(wù)處理函數(shù)
func?serveWs(w?http.ResponseWriter,?r?*http.Request)?{
?fmt.Println(r.Host)
?//?將連接更新為?WebSocket?連接
?ws,?err?:=?upgrader.Upgrade(w,?r,?nil)
?if?err?!=?nil?{
??log.Println(err)
?}
?//?一直監(jiān)聽?WebSocket?連接上傳來的新消息
?reader(ws)
}
func?setupRoutes()?{
?http.HandleFunc("/",?func(w?http.ResponseWriter,?r?*http.Request)?{
??fmt.Fprintf(w,?"Simple?Server")
?})
?//?將?`/ws`?端點(diǎn)交給?`serveWs`?函數(shù)處理
?http.HandleFunc("/ws",?serveWs)
}
func?main()?{
?fmt.Println("Chat?App?v0.01")
?setupRoutes()
?http.ListenAndServe(":8080",?nil)
}
如果沒有問題的話,我們使用 go run main.go 來啟動服務(wù)。
客戶端
現(xiàn)在已經(jīng)設(shè)置好了服務(wù),我們需要一些能夠與之交互的東西。這是我們的 ReactJS 前端發(fā)揮作用的地方。
我們先盡量讓客戶端保持簡單,并定義一個 api/index.js 文件,它將包含 WebSocket 連接的代碼。
//?api/index.js
var?socket?=?new?WebSocket("ws://localhost:8080/ws");
let?connect?=?()?=>?{
?console.log("Attempting?Connection...");
?socket.onopen?=?()?=>?{
??console.log("Successfully?Connected");
?};
?socket.onmessage?=?msg?=>?{
??console.log(msg);
?};
?socket.onclose?=?event?=>?{
??console.log("Socket?Closed?Connection:?",?event);
?};
?socket.onerror?=?error?=>?{
??console.log("Socket?Error:?",?error);
?};
};
let?sendMsg?=?msg?=>?{
?console.log("sending?msg:?",?msg);
?socket.send(msg);
};
export?{?connect,?sendMsg?};
因此,在上面的代碼中,我們定義了我們隨后導(dǎo)出的 2 個函數(shù)。分別是 connect() 和 sendMsg(msg)。
第一個函數(shù),connect() 函數(shù),連接 WebSocket 端點(diǎn),并監(jiān)聽例如與 onopen 成功連接之類的事件。如果它發(fā)現(xiàn)任何問題,例如連接關(guān)閉的套接字或錯誤,它會將這些問題打印到瀏覽器控制臺。
第二個函數(shù),sendMsg(msg) 函數(shù),允許我們使用 socket.send() 通過 WebSocket 連接從前端發(fā)送消息到后端。
現(xiàn)在我們在 React 項(xiàng)目中更新 App.js 文件,添加對 connect() 的調(diào)用并創(chuàng)建一個觸發(fā) sendMsg() 函數(shù)的 元素。
//?App.js
import?React,?{?Component?}?from?"react";
import?"./App.css";
import?{?connect,?sendMsg?}?from?"./api";
class?App?extends?Component?{
?constructor(props)?{
??super(props);
??connect();
?}
?send()?{
??console.log("hello");
??sendMsg("hello");
?}
?render()?{
??return?(
???<div?className="App">
????<button?onClick={this.send}>Hitbutton>
???div>
??);
?}
}
export?default?App;
使用 npm start 成功編譯后,我們可以在瀏覽器中看到一個按鈕,如果打開瀏覽器控制臺,還可以看到成功連接的 WebSocket 服務(wù)運(yùn)行在 http://localhost:8080[5]。
問題 - 單擊此按鈕會發(fā)生什么?你在瀏覽器的控制臺和后端的控制臺中看到了什么輸出?
總結(jié)
結(jié)束了本系列的第 2 部分。我們已經(jīng)能夠創(chuàng)建一個非常簡單的 WebSocket 服務(wù),它可以回顯發(fā)送給它的任何消息。
這是開發(fā)應(yīng)用程序的關(guān)鍵一步,現(xiàn)在我們已經(jīng)啟動并運(yùn)行了基本框架,我們可以開始考慮實(shí)現(xiàn)基本的聊天功能并讓這個程序變得更有用!
下一節(jié):Part 3 - 前端實(shí)現(xiàn)[6]
via: https://tutorialedge.net/projects/chat-system-in-go-and-react/part-2-simple-communication/
作者:Elliot Forbes[7]譯者:咔嘰咔嘰[8]校對:polaris1119[9]
本文由 GCTT[10] 原創(chuàng)編譯,Go 中文網(wǎng)[11] 榮譽(yù)推出
參考資料
GitHub: https://github.com/watermelo/realtime-chat-go-react/tree/part-1-and-2
[2]初始化設(shè)置: https://studygolang.com/articles/22423
[3]http://localhost:8080: http://localhost:8080
[4]RFC-6455: https://tools.ietf.org/html/rfc6455
[5]http://localhost:8080: http://localhost:8080
[6]前端實(shí)現(xiàn): https://studygolang.com/articles/22429
[7]Elliot Forbes: https://twitter.com/elliot_f
[8]咔嘰咔嘰: https://github.com/watermelo
[9]polaris1119: https://github.com/polaris1119
[10]GCTT: https://github.com/studygolang/GCTT
[11]Go 中文網(wǎng): https://studygolang.com/
推薦閱讀
