教學推薦:node js教學、React教學、WebSocket教學
Web 為了支援客戶端和伺服器之間的全雙工(或雙向)通訊已經走過了很長的路。這是 WebSocket 協定的主要目的:透過單一 TCP 套接字連接在客戶端和伺服器之間提供持久的即時通訊。
WebSocket 協定只有兩個議程:1)開啟握手,2)幫助資料傳輸。一旦伺服器和客戶端握手成功,他們就可以隨意地以較少的開銷相互發送資料。
WebSocket 通訊使用WS(連接埠80)或WSS(連接埠443)協定在單一 TCP 套接字上進行。根據 Can I Use,撰寫本文時除了 Opera Mini 之外幾乎所有的瀏覽器支援 WebSockets 。
從歷史上看,創建需要即時資料通訊(如遊戲或聊天應用程式)的 Web 應用程式需要濫用 HTTP 協定來建立雙向資料傳輸。儘管有許多種方法用於實現即時功能,但沒有一種方法與 WebSockets 一樣有效率。 HTTP 輪詢、HTTP流、Comet、SSE —— 它們都有自己的缺點。
解決問題的第一個嘗試是定期輪詢伺服器。 HTTP 長輪詢生命週期如下:
長輪詢中存在著許多漏洞 —— 標頭開銷、延遲、逾時、快取等等。
這種機制減少了網路延遲的痛苦,因為初始請求無限期地保持開啟。即使在伺服器推送資料之後,請求也永遠不會終止。 HTTP 流中的前三步驟生命週期方法與 HTTP 輪詢是相同的。
但是,當回應傳送回客戶端時,請求永遠不會終止,伺服器保持連線開啟狀態,並在發生變更時發送新的更新。
使用 SSE,伺服器將資料推送到客戶端。聊天或遊戲應用程式不能完全依賴 SSE。 SSE 的完美用例是類似 Facebook 的新聞 Feed:每當有新貼文發佈時,伺服器會將它們推送到時間軸。 SSE 透過傳統 HTTP 傳送,並且對開啟的連線數有限制。
這些方法不僅效率低下,維護它們的程式碼也使開發人員感到厭倦。
WebSockets 旨在取代現有的雙向通訊技術。當涉及全雙工即時通訊時,上述現有方法既不可靠也不高效。
WebSockets 類似於 SSE,但在將訊息從客戶端傳回伺服器方面也很優秀。由於資料是透過單一 TCP 套接字連接提供的,因此連線限制不再是問題。
如同介紹中所提到的,WebSocket 協定只有兩個議程。讓我們看看 WebSockets 如何實現這些議程。為此我將分析一個 Node.js 伺服器並將其連接到使用 React.js 建置的客戶端。
我們可以用單一連接埠分別提供 HTTP 服務和 WebSocket 服務。下面的程式碼顯示了一個簡單的 HTTP 伺服器的建立過程。一旦創建,我們會將 WebSocket 伺服器綁定到 HTTP 連接埠:
const webSocketsServerPort = 8000; const webSocketServer = require('websocket').server; const http = require('http'); // Spinning the http server and the websocket server. const server = http.createServer(); server.listen(webSocketsServerPort); const wsServer = new webSocketServer({ httpServer: server });
建立 WebSocket 伺服器後,我們需要在接收來自客戶端的請求時接受握手。我將所有連線的客戶端作為物件保存在程式碼中,並在收請從瀏覽器發送的求時使用唯一的使用者ID。
// I'm maintaining all active connections in this object const clients = {}; // This code generates unique userid for everyuser. const getUniqueID = () => { const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); return s4() + s4() + '-' + s4(); }; wsServer.on('request', function(request) { var userID = getUniqueID(); console.log((new Date()) + ' Recieved a new connection from origin ' + request.origin + '.'); // You can rewrite this part of the code to accept only the requests from allowed origin const connection = request.accept(null, request.origin); clients[userID] = connection; console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(clients)) });
那麼,當接受連線時會發生什麼事?
在傳送常規 HTTP 請求以建立連線時,在請求頭中,用戶端會傳送 *Sec-WebSocket-Key*
。伺服器對此值進行編碼和雜湊,並新增預先定義的 GUID。它回應了伺服器發送的握手中 *Sec-WebSocket-Accept*
中產生的值。
一旦請求在伺服器中被接受(在必要驗證之後),就完成了握手,其狀態代碼為 101
。如果在瀏覽器中看到除狀態碼 101
之外的任何內容,則表示 WebSocket 升級失敗,並且將遵循正常的 HTTP 語意。
*Sec-WebSocket-Accept*
头字段指示服务器是否愿意接受连接。此外如果响应缺少 *Upgrade*
头字段,或者 *Upgrade*
不等于 websocket
,则表示 WebSocket 连接失败。
成功的服务器握手如下所示:
HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols Connection: Upgrade Sec-WebSocket-Accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw= Upgrade: websocket
在客户端,我使用与服务器中的相同 WebSocket 包来建立与服务器的连接(Web IDL 中的 WebSocket API 正在由W3C 进行标准化)。一旦服务器接受请求,我们将会在浏览器控制台上看到 WebSocket Client Connected
。
这是创建与服务器的连接的初始脚手架:
import React, { Component } from 'react'; import { w3cwebsocket as W3CWebSocket } from "websocket"; const client = new W3CWebSocket('ws://127.0.0.1:8000'); class App extends Component { componentWillMount() { client.onopen = () => { console.log('WebSocket Client Connected'); }; client.onmessage = (message) => { console.log(message); }; } render() { return ( <div> Practical Intro To WebSockets. </div> ); } } export default App;
客户端发送以下标头来建立握手:
HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: vISxbQhM64Vzcr/CD7WHnw== Origin: http://localhost:3000 Sec-WebSocket-Version: 13
现在客户端和服务器通过相互握手进行了连接,WebSocket 连接可以在接收消息时传输消息,从而实现 WebSocket 协议的第二个议程。
我将编写一个基本的实时文档编辑器,用户可以将它们连接在一起并编辑文档。我跟踪了两个事件:
该协议允许我们用二进制数据或 UTF-8 发送和接收消息(注意:传输和转换 UTF-8 的开销较小)。
只要我们对套接字事件onopen
、onclose
和 onmessage
有了充分的了解,理解和实现 WebSockets 就非常简单。客户端和服务器端的术语相同。
在客户端,当新用户加入或内容更改时,我们用 client.send
向服务器发消息,以将新信息提供给服务器。
/* When a user joins, I notify the server that a new user has joined to edit the document. */ logInUser = () => { const username = this.username.value; if (username.trim()) { const data = { username }; this.setState({ ...data }, () => { client.send(JSON.stringify({ ...data, type: "userevent" })); }); } } /* When content changes, we send the current content of the editor to the server. */ onEditorStateChange = (text) => { client.send(JSON.stringify({ type: "contentchange", username: this.state.username, content: text })); };
我们跟踪的事件是:用户加入和内容更改。
从服务器接收消息非常简单:
componentWillMount() { client.onopen = () => { console.log('WebSocket Client Connected'); }; client.onmessage = (message) => { const dataFromServer = JSON.parse(message.data); const stateToChange = {}; if (dataFromServer.type === "userevent") { stateToChange.currentUsers = Object.values(dataFromServer.data.users); } else if (dataFromServer.type === "contentchange") { stateToChange.text = dataFromServer.data.editorContent || contentDefaultMessage; } stateToChange.userActivity = dataFromServer.data.userActivity; this.setState({ ...stateToChange }); }; }
在服务器中,我们只需捕获传入的消息并将其广播到连接到 WebSocket 的所有客户端。这是臭名昭着的 Socket.IO 和 WebSocket 之间的差异之一:当我们使用 WebSockets 时,我们需要手动将消息发送给所有客户端。 Socket.IO 是一个成熟的库,所以它自己来处理。
const sendMessage = (json) => { // We are sending the current data to all connected clients Object.keys(clients).map((client) => { clients[client].sendUTF(json); }); } connection.on('message', function(message) { if (message.type === 'utf8') { const dataFromClient = JSON.parse(message.utf8Data); const json = { type: dataFromClient.type }; if (dataFromClient.type === typesDef.USER_EVENT) { users[userID] = dataFromClient; userActivity.push(`${dataFromClient.username} joined to edit the document`); json.data = { users, userActivity }; } else if (dataFromClient.type === typesDef.CONTENT_CHANGE) { editorContent = dataFromClient.content; json.data = { editorContent, userActivity }; } sendMessage(JSON.stringify(json)); } });
将消息广播到所有连接的客户端。
在这种情况下,WebSocket调用 close
事件,它允许我们编写终止当前用户连接的逻辑。在我的代码中,当用户离开文档时,会向其余用户广播消息:
connection.on('close', function(connection) { console.log((new Date()) + " Peer " + userID + " disconnected."); const json = { type: typesDef.USER_EVENT }; userActivity.push(`${users[userID].username} left the document`); json.data = { users, userActivity }; delete clients[userID]; delete users[userID]; sendMessage(JSON.stringify(json)); });
该应用程序的源代码位于GitHub上的 repo 中。
WebSockets 是在应用中实现实时功能的最有趣和最方便的方法之一。它为我们提供了能够充分利用全双工通信的灵活性。我强烈建议在尝试使用 Socket.IO 和其他可用库之前先试试 WebSockets。
编码快乐!
更多编程相关知识,请访问:编程教学!!
以上是Node和React中如何進行即時通訊?的詳細內容。更多資訊請關注PHP中文網其他相關文章!