WebSocket 協議 - 實戰
本文是 WebSocket 系列文章的第 2 篇,第 1 篇主要講述概念原理。本文從實戰角度介紹如何使用 WebSocket。
Part1 項目簡介
本文實戰項目來自 Mastering WebSockets With Go。實現了一個精簡版的 Web 聊天系統,前端採用 HTML+JS,後端用 Go 實現。注意: 本文對原項目做了一點 UI 顏色調整
在本地構建部署後效果如下,先要登錄後創建 WebSocket 連接,然後可以向聊天室發送信息。
Part2 關鍵 API
1 客戶端 API
下面是 javascript 創建 WebSocket 代碼,直接通過構造函數 new WebSocket 創建一個實例。
conn = new WebSocket("wss://" + document.location.host + "/ws?otp="+ otp);
// Onopen
conn.onopen = function (evt) {
document.getElementById("connection-header").innerHTML = "Connected to Websocket: true";
}
conn.onclose = function(evt) {
// Set disconnected
document.getElementById("connection-header").innerHTML = "Connected to Websocket: false";
}
// Add a listener to the onmessage event
conn.onmessage = function (evt) {
console.log(evt);
// parse websocket message as JSON
const eventData = JSON.parse(evt.data);
// Assign JSON data to new Event Object
const event = Object.assign(new Event, eventData);
// Let router manage message
routeEvent(event);
}
WebSocket 構造函數
WebSocket 接收一個 URL 參數,執行完下面語句後,就會與對應的 host 建立連接。
conn = new WebSocket("wss://" + document.location.host + "/ws?otp="+ otp)
onopen
WebSocket 實例對象的 onopen 屬性,在上面的連接建立成功後進行回調執行。
conn.onopen = function (evt) {
document.getElementById("connection-header").innerHTML = "Connected to Websocket: true";
}
這裏會在連接成功後,頁面顯示 "Connected to Websocket: true"
onmessage
收到來自服務端數據後的回調函數, 處理邏輯在routeEvent
.
conn.onmessage = function (evt) {
console.log(evt);
// parse websocket message as JSON
const eventData = JSON.parse(evt.data);
// Assign JSON data to new Event Object
const event = Object.assign(new Event, eventData);
// Let router manage message
routeEvent(event);
}
routeEvent 先進行一些參數校驗,然後構造 messageEvent 丟給 appendChatMessage。
function routeEvent(event) {
if (event.type === undefined) {
alert("no 'type' field in event");
}
switch (event.type) {
case "new_message":
// Format payload
const messageEvent = Object.assign(new NewMessageEvent, event.payload);
appendChatMessage(messageEvent);
break;
default:
alert("unsupported message type");
break;
}
}
真正顯示處理在appendChatMessage
函數。在消息前加上日期時間,並拼接到聊天室之前內容的尾部。
function appendChatMessage(messageEvent) {
var date = new Date(messageEvent.sent);
// format message
const formattedMsg = `${date.toLocaleString()}: ${messageEvent.message}`;
// Append Message
textarea = document.getElementById("chatmessages");
textarea.innerHTML = textarea.innerHTML + "\n" + formattedMsg;
textarea.scrollTop = textarea.scrollHeight;
}
onclose
WebSocket 連接關閉後的回調函數。在頁面上顯示 "Connected to Websocket: false"。
conn.onclose = function(evt) {
// Set disconnected
document.getElementById("connection-header").innerHTML = "Connected to Websocket: false";
}
send
send 方法向服務端發送數據。這裏把 payload 數據封裝成 Event json 格式發送給後端。
function sendEvent(eventName, payload) {
// Create a event Object with a event named send_message
const event = new Event(eventName, payload);
// Format as JSON and send
conn.send(JSON.stringify(event));
}
2 服務端 API
服務端採用的是開源的 github.com/gorilla/websocket。是用 Go 語言實現的 WebSocket 庫。該庫簡單易用、性能比較高。
Upgrade
調用 websocket.Upgrader 對象的 Upgrade 方法將普通的 HTTP 連接升級爲 WebSocket 連接。函數簽名如下, 直接將 http 的 w 和 r 傳遞給它即可。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)
url 路由爲 / ws 即走 WebSocket 協議,可以看到 serveWS 中調用 conn, err := websocketUpgrader.Upgrade(w, r, nil)
將 HTTP 請求升級爲 WebSocket.
func (m *Manager) serveWS(w http.ResponseWriter, r *http.Request) {
otp := r.URL.Query().Get("otp")
if otp == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
if !m.otps.VerifyOTP(otp) {
w.WriteHeader(http.StatusUnauthorized)
return
}
log.Println("New connection")
conn, err := websocketUpgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := NewClient(conn, m)
m.addClient(client)
go client.readMessages()
go client.writeMessages()
}
ReadMessage
調用 ReadMessage 讀取客戶端發送的數據,該方法返回 3 個參數,第一個參數表示讀取的數據類型,第二參數是讀取到的數據,第三個參數爲 error 類型。
業務層面數據類型主要是 TextMessage 和 BinaryMessage,分別表示讀取到數據是文本數據還是二進制數據。
WriteMessage
調用 WriteMessage 向客戶端發送數據,需要傳入兩個參數,第一個參數表示數據類型,這裏傳輸的是文本數據。第二個參數傳數據內容。
Part3 總結
服務端用短短的幾百行代碼實現了一個完整的 WebSocket 服務器框架,實現結構圖如下,兼具安全性和擴展性,值得借鑑模仿。
以下幾點內容值得學習:
- 使用 Ping/Pong 心跳技術保活
- 限制消息大小避免惡意攻擊
- Origin Check 跨越檢查
- Authentication 身份驗證
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Y6WmPw8pjzcRFmcyNYzh7w