成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

WebSockets實(shí)戰(zhàn):在 Node 和 React 之間進(jìn)行實(shí)時(shí)通信

DevWiki / 3139人閱讀

摘要:服務(wù)器推遲響應(yīng),直到發(fā)生更改更新或超時(shí)。旨在取代現(xiàn)有的雙向通信技術(shù)。只要我們對(duì)套接字事件和有了充分的了解,理解和實(shí)現(xiàn)就非常簡(jiǎn)單。

翻譯:瘋狂的技術(shù)宅

原文:blog.logrocket.com/websockets-…

Web 為了支持客戶端和服務(wù)器之間的全雙工(或雙向)通信已經(jīng)走過了很長(zhǎng)的路。這是 WebSocket 協(xié)議的主要目的:通過單個(gè) TCP 套接字連接在客戶端和服務(wù)器之間提供持久的實(shí)時(shí)通信。

WebSocket 協(xié)議只有兩個(gè)議程:1)打開握手,2)幫助數(shù)據(jù)傳輸。一旦服務(wù)器和客戶端握手成功,他們就可以隨意地以較少的開銷相互發(fā)送數(shù)據(jù)。

WebSocket 通信使用WS(端口80)或WSS(端口443)協(xié)議在單個(gè) TCP 套接字上進(jìn)行。根據(jù) Can I Use,撰寫本文時(shí)除了 Opera Mini 之外幾乎所有的瀏覽器支持 WebSockets 。

現(xiàn)狀

從歷史上看,創(chuàng)建需要實(shí)時(shí)數(shù)據(jù)通訊(如游戲或聊天應(yīng)用程序)的 Web 應(yīng)用需要濫用 HTTP 協(xié)議來(lái)建立雙向數(shù)據(jù)傳輸。盡管有許多種方法用于實(shí)現(xiàn)實(shí)時(shí)功能,但沒有一種方法與 WebSockets 一樣高效。 HTTP 輪詢、HTTP流、Comet、SSE —— 它們都有自己的缺點(diǎn)。

HTTP 輪詢

解決問題的第一個(gè)嘗試是定期輪詢服務(wù)器。 HTTP 長(zhǎng)輪詢生命周期如下:

    客戶端發(fā)出請(qǐng)求并一直等待響應(yīng)。

    服務(wù)器推遲響應(yīng),直到發(fā)生更改、更新或超時(shí)。請(qǐng)求保持“掛起”,直到服務(wù)器有東西返回客戶端。

    當(dāng)服務(wù)器端有一些更改或更新時(shí),它會(huì)將響應(yīng)發(fā)送回客戶端。

    客戶端發(fā)送新的長(zhǎng)輪詢請(qǐng)求以偵聽下一組更改。

長(zhǎng)輪詢中存在很多漏洞 —— 標(biāo)頭開銷、延遲、超時(shí)、緩存等等。

HTTP 流式傳輸

這種機(jī)制減少了網(wǎng)絡(luò)延遲的痛苦,因?yàn)槌跏颊?qǐng)求無(wú)限期地保持打開狀態(tài)。即使在服務(wù)器推送數(shù)據(jù)之后,請(qǐng)求也永遠(yuǎn)不會(huì)終止。 HTTP 流中的前三步生命周期方法與 HTTP 輪詢是相同的。

但是,當(dāng)響應(yīng)被發(fā)送回客戶端時(shí),請(qǐng)求永遠(yuǎn)不會(huì)終止,服務(wù)器保持連接打開狀態(tài),并在發(fā)生更改時(shí)發(fā)送新的更新。

服務(wù)器發(fā)送事件(SSE)

使用 SSE,服務(wù)器將數(shù)據(jù)推送到客戶端。聊天或游戲應(yīng)用不能完全依賴 SSE。 SSE 的完美用例是類似 Facebook 的新聞 Feed:每當(dāng)有新帖發(fā)布時(shí),服務(wù)器會(huì)將它們推送到時(shí)間線。 SSE 通過傳統(tǒng) HTTP 發(fā)送,并且對(duì)打開的連接數(shù)有限制。

這些方法不僅效率低下,維護(hù)它們的代碼也使開發(fā)人員感到厭倦。

WebSocket

WebSockets 旨在取代現(xiàn)有的雙向通信技術(shù)。當(dāng)涉及全雙工實(shí)時(shí)通信時(shí),上述現(xiàn)有方法既不可靠也不高效。

WebSockets 類似于 SSE,但在將消息從客戶端傳回服務(wù)器方面也很優(yōu)秀。由于數(shù)據(jù)是通過單個(gè) TCP 套接字連接提供的,因此連接限制不再是問題。


實(shí)戰(zhàn)教程

正如介紹中所提到的,WebSocket 協(xié)議只有兩個(gè)議程。讓我們看看 WebSockets 如何實(shí)現(xiàn)這些議程。為此我將分析一個(gè) Node.js 服務(wù)器并將其連接到使用 React.js 構(gòu)建的客戶端上。

議程1:WebSocket在服務(wù)器和客戶端之間建立握手

在服務(wù)器級(jí)別創(chuàng)建握手

我們可以用單個(gè)端口來(lái)分別提供 HTTP 服務(wù)和 WebSocket 服務(wù)。下面的代碼顯示了一個(gè)簡(jiǎn)單的 HTTP 服務(wù)器的創(chuàng)建過程。一旦創(chuàng)建,我們會(huì)將 WebSocket 服務(wù)器綁定到 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
});

創(chuàng)建 WebSocket 服務(wù)器后,我們需要在接收來(lái)自客戶端的請(qǐng)求時(shí)接受握手。我將所有連接的客戶端作為對(duì)象保存在代碼中,并在收請(qǐng)從瀏覽器發(fā)來(lái)的求時(shí)使用唯一的用戶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))
});

那么,當(dāng)接受連接時(shí)會(huì)發(fā)生什么?

在發(fā)送常規(guī) HTTP 請(qǐng)求以建立連接時(shí),在請(qǐng)求頭中,客戶端發(fā)送 *Sec-WebSocket-Key*。服務(wù)器對(duì)此值進(jìn)行編碼和散列,并添加預(yù)定義的 GUID。它回應(yīng)了服務(wù)器發(fā)送的握手中 *Sec-WebSocket-Accept*中生成的值。

一旦請(qǐng)求在服務(wù)器中被接受(在必要驗(yàn)證之后),就完成了握手,其狀態(tài)代碼為 101。如果在瀏覽器中看到除狀態(tài)碼 101 之外的任何內(nèi)容,則意味著 WebSocket 升級(jí)失敗,并且將遵循正常的 HTTP 語(yǔ)義。

*Sec-WebSocket-Accept* 頭字段指示服務(wù)器是否愿意接受連接。此外如果響應(yīng)缺少 *Upgrade* 頭字段,或者 *Upgrade* 不等于 websocket,則表示 WebSocket 連接失敗。

成功的服務(wù)器握手如下所示:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw=
Upgrade: websocket

在客戶端級(jí)別創(chuàng)建握手

在客戶端,我使用與服務(wù)器中的相同 WebSocket 包來(lái)建立與服務(wù)器的連接(Web IDL 中的 WebSocket API 正在由W3C 進(jìn)行標(biāo)準(zhǔn)化)。一旦服務(wù)器接受請(qǐng)求,我們將會(huì)在瀏覽器控制臺(tái)上看到 WebSocket Client Connected。

這是創(chuàng)建與服務(wù)器的連接的初始腳手架:

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;

客戶端發(fā)送以下標(biāo)頭來(lái)建立握手:

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

現(xiàn)在客戶端和服務(wù)器通過相互握手進(jìn)行了連接,WebSocket 連接可以在接收消息時(shí)傳輸消息,從而實(shí)現(xiàn) WebSocket 協(xié)議的第二個(gè)議程。

議程2:實(shí)時(shí)信息傳輸

我將編寫一個(gè)基本的實(shí)時(shí)文檔編輯器,用戶可以將它們連接在一起并編輯文檔。我跟蹤了兩個(gè)事件:

    **用戶活動(dòng):**每次用戶加入或離開時(shí),我都會(huì)將消息廣播給所有連接其他的客戶端。

    **內(nèi)容更改:**每次修改編輯器中的內(nèi)容時(shí),都會(huì)向所有連接的其他客戶端廣播。

該協(xié)議允許我們用二進(jìn)制數(shù)據(jù)或 UTF-8 發(fā)送和接收消息(注意:傳輸和轉(zhuǎn)換 UTF-8 的開銷較小)。

只要我們對(duì)套接字事件onopen、oncloseonmessage有了充分的了解,理解和實(shí)現(xiàn) WebSockets 就非常簡(jiǎn)單??蛻舳撕头?wù)器端的術(shù)語(yǔ)相同。

在客戶端發(fā)送和接收消息

在客戶端,當(dāng)新用戶加入或內(nèi)容更改時(shí),我們用 client.send 向服務(wù)器發(fā)消息,以將新信息提供給服務(wù)器。

/* 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
 }));
};

我們跟蹤的事件是:用戶加入和內(nèi)容更改。

從服務(wù)器接收消息非常簡(jiǎn)單:

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
    });
  };
}

在服務(wù)器端發(fā)送和偵聽消息

在服務(wù)器中,我們只需捕獲傳入的消息并將其廣播到連接到 WebSocket 的所有客戶端。這是臭名昭著的 Socket.IO 和 WebSocket 之間的差異之一:當(dāng)我們使用 WebSockets 時(shí),我們需要手動(dòng)將消息發(fā)送給所有客戶端。 Socket.IO 是一個(gè)成熟的庫(kù),所以它自己來(lái)處理。

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));
    }
  });

將消息廣播到所有連接的客戶端。

瀏覽器關(guān)閉后會(huì)發(fā)生什么?

在這種情況下,WebSocket調(diào)用 close 事件,它允許我們編寫終止當(dāng)前用戶連接的邏輯。在我的代碼中,當(dāng)用戶離開文檔時(shí),會(huì)向其余用戶廣播消息:

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));
  });

該應(yīng)用程序的源代碼位于GitHub上的 repo 中。

結(jié)論

WebSockets 是在應(yīng)用中實(shí)現(xiàn)實(shí)時(shí)功能的最有趣和最方便的方法之一。它為我們提供了能夠充分利用全雙工通信的靈活性。我強(qiáng)烈建議在嘗試使用 Socket.IO 和其他可用庫(kù)之前先試試 WebSockets。

編碼快樂!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/6908.html

相關(guān)文章

  • JavaScript是如何工作: 深入探索 websocket HTTP/2與SSE +如何選擇正

    摘要:數(shù)據(jù)作為消息通過傳輸,每個(gè)消息由一個(gè)或多個(gè)幀組成,其中包含正在發(fā)送的數(shù)據(jù)有效負(fù)載。幀數(shù)據(jù)如上所述,數(shù)據(jù)可以被分割成多個(gè)幀。但是,規(guī)范希望能夠處理交錯(cuò)的控制幀。 文章底部分享給大家一套 react + socket 實(shí)戰(zhàn)教程 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第5篇。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過了前面的章...

    cuieney 評(píng)論0 收藏0
  • 前端每周清單第 38 期: Node 9 發(fā)布,Kotlin 與 React,Netflix 架構(gòu)解

    摘要:發(fā)布本周正式發(fā)布,包含了一系列的特性提升與問題修復(fù),同時(shí)也在不斷致力于將打造地更為輕巧與高性能。當(dāng)然,姜振勇老師還會(huì)介紹的多種服務(wù),包括大數(shù)據(jù)網(wǎng)絡(luò)和安全,展現(xiàn)彈性安全和高可擴(kuò)展性的全方位能力。 showImg(http://upload-images.jianshu.io/upload_images/1647496-2ce7598e6987d9af.jpg?imageMogr2/aut...

    Carbs 評(píng)論0 收藏0
  • JavaScript 工作原理之五-深入理解 WebSockets 帶有 SSE 機(jī)制的HTTP/

    摘要:幀協(xié)議讓我們深入了解下幀協(xié)議。目前可用的值該幀接續(xù)前面一幀的有效載荷。該幀包含二進(jìn)制數(shù)據(jù)。幀有以下幾類長(zhǎng)度表示有效載荷的長(zhǎng)度。數(shù)據(jù)分片有效載荷數(shù)據(jù)可以被分成多個(gè)獨(dú)立的幀。接收端會(huì)緩沖這些幀直到位有值。 原文請(qǐng)查閱這里,略有改動(dòng),本文采用知識(shí)共享署名 3.0 中國(guó)大陸許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原...

    lijinke666 評(píng)論0 收藏0
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2SSE,以及如何選擇

    摘要:注意值得注意的是,一旦接收到所有幀并且原始消息有效載荷已被重建,客戶端將僅被通知關(guān)于新消息。實(shí)驗(yàn)表明,在之后創(chuàng)建了第二個(gè)幀。以下值目前正在使用中代表繼續(xù)幀。 這一次,我們將深入到通信協(xié)議的世界中,對(duì)比并討論它們的屬性并構(gòu)建部件。我們將提供WebSockets和HTTP / 2的快速比較。 最后,我們分享一些關(guān)于如何選擇網(wǎng)絡(luò)協(xié)議。 概述 如今,擁有豐富動(dòng)態(tài)用戶界面的復(fù)雜網(wǎng)絡(luò)應(yīng)用程序被視為...

    calx 評(píng)論0 收藏0
  • Nodejs爬蟲實(shí)戰(zhàn)項(xiàng)目之鏈家

    摘要:很基礎(chǔ),不喜勿噴轉(zhuǎn)載注明出處爬蟲實(shí)戰(zhàn)項(xiàng)目之鏈家效果圖思路爬蟲究竟是怎么實(shí)現(xiàn)的通過訪問要爬取的網(wǎng)站地址,獲得該頁(yè)面的文檔內(nèi)容,找到我們需要保存的數(shù)據(jù),進(jìn)一步查看數(shù)據(jù)所在的元素節(jié)點(diǎn),他們?cè)谀撤矫嬉欢ㄊ怯幸?guī)律的,遵循規(guī)律,操作,保存數(shù)據(jù)。 說(shuō)明 作為一個(gè)前端界的小學(xué)生,一直想著自己做一些項(xiàng)目向全棧努力。愁人的是沒有后臺(tái),搜羅之后且學(xué)會(huì)了nodejs和express寫成本地的接口給前端頁(yè)面調(diào)用...

    noONE 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<