摘要:服務器推遲響應,直到發生更改更新或超時。旨在取代現有的雙向通信技術。只要我們對套接字事件和有了充分的了解,理解和實現就非常簡單。
翻譯:瘋狂的技術宅
原文:blog.logrocket.com/websockets-…
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 發送,并且對打開的連接數有限制。
這些方法不僅效率低下,維護它們的代碼也使開發人員感到厭倦。
WebSocketWebSockets 旨在取代現有的雙向通信技術。當涉及全雙工實時通信時,上述現有方法既不可靠也不高效。
WebSockets 類似于 SSE,但在將消息從客戶端傳回服務器方面也很優秀。由于數據是通過單個 TCP 套接字連接提供的,因此連接限制不再是問題。
正如介紹中所提到的,WebSocket 協議只有兩個議程。讓我們看看 WebSockets 如何實現這些議程。為此我將分析一個 Node.js 服務器并將其連接到使用 React.js 構建的客戶端上。
議程1:WebSocket在服務器和客戶端之間建立握手我們可以用單個端口來分別提供 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 協議的第二個議程。
議程2:實時信息傳輸
我將編寫一個基本的實時文檔編輯器,用戶可以將它們連接在一起并編輯文檔。我跟蹤了兩個事件:
**用戶活動:**每次用戶加入或離開時,我都會將消息廣播給所有連接其他的客戶端。
**內容更改:**每次修改編輯器中的內容時,都會向所有連接的其他客戶端廣播。
該協議允許我們用二進制數據或 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。
編碼快樂!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6908.html
摘要:數據作為消息通過傳輸,每個消息由一個或多個幀組成,其中包含正在發送的數據有效負載。幀數據如上所述,數據可以被分割成多個幀。但是,規范希望能夠處理交錯的控制幀。 文章底部分享給大家一套 react + socket 實戰教程 這是專門探索 JavaScript 及其所構建的組件的系列文章的第5篇。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 如果你錯過了前面的章...
摘要:發布本周正式發布,包含了一系列的特性提升與問題修復,同時也在不斷致力于將打造地更為輕巧與高性能。當然,姜振勇老師還會介紹的多種服務,包括大數據網絡和安全,展現彈性安全和高可擴展性的全方位能力。 showImg(http://upload-images.jianshu.io/upload_images/1647496-2ce7598e6987d9af.jpg?imageMogr2/aut...
摘要:幀協議讓我們深入了解下幀協議。目前可用的值該幀接續前面一幀的有效載荷。該幀包含二進制數據。幀有以下幾類長度表示有效載荷的長度。數據分片有效載荷數據可以被分成多個獨立的幀。接收端會緩沖這些幀直到位有值。 原文請查閱這里,略有改動,本文采用知識共享署名 3.0 中國大陸許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:注意值得注意的是,一旦接收到所有幀并且原始消息有效載荷已被重建,客戶端將僅被通知關于新消息。實驗表明,在之后創建了第二個幀。以下值目前正在使用中代表繼續幀。 這一次,我們將深入到通信協議的世界中,對比并討論它們的屬性并構建部件。我們將提供WebSockets和HTTP / 2的快速比較。 最后,我們分享一些關于如何選擇網絡協議。 概述 如今,擁有豐富動態用戶界面的復雜網絡應用程序被視為...
摘要:很基礎,不喜勿噴轉載注明出處爬蟲實戰項目之鏈家效果圖思路爬蟲究竟是怎么實現的通過訪問要爬取的網站地址,獲得該頁面的文檔內容,找到我們需要保存的數據,進一步查看數據所在的元素節點,他們在某方面一定是有規律的,遵循規律,操作,保存數據。 說明 作為一個前端界的小學生,一直想著自己做一些項目向全棧努力。愁人的是沒有后臺,搜羅之后且學會了nodejs和express寫成本地的接口給前端頁面調用...
閱讀 1626·2021-10-14 09:43
閱讀 5503·2021-09-07 10:21
閱讀 1275·2019-08-30 15:56
閱讀 2123·2019-08-30 15:53
閱讀 1231·2019-08-30 15:44
閱讀 2010·2019-08-30 15:44
閱讀 1320·2019-08-29 17:24
閱讀 752·2019-08-29 15:19