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 流式传输

这种机制减少了网络延迟的痛苦,因为初始请求无限期地保持打开状态。即使在服务器推送数据之后,请求也永远不会终止。 HTTP 流中的前三步生命周期方法与 HTTP 轮询是相同的。

但是,当响应被发送回客户端时,请求永远不会终止,服务器保持连接打开状态,并在发生更改时发送新的更新。

服务器发送事件(SSE)

使用 SSE,服务器将数据推送到客户端。聊天或游戏应用不能完全依赖 SSE。 SSE 的完美用例是类似 Facebook 的新闻 Feed:每当有新帖发布时,服务器会将它们推送到时间线。 SSE 通过传统 HTTP 发送,并且对打开的连接数有限制。

这些方法不仅效率低下,维护它们的代码也使开发人员感到厌倦。

WebSocket

WebSockets 旨在取代现有的双向通信技术。当涉及全双工实时通信时,上述现有方法既不可靠也不高效。

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 (

Practical Intro To WebSockets.

);

}

}

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。

编码快乐!

更多编程相关知识,请访问:编程教学!!

php reactphp wss_Node和React中如何进行实时通信?相关推荐

  1. 处理 react_【学习教程】React 中阻止事件冒泡的问题

    来源 | https://www.cnblogs.com/Wayou/p/react_event_issue.html 在正式开始前,先来看看 js 中事件的触发与事件处理器的执行. js 中事件的监 ...

  2. react中ref的使用

    在react中获取真实dom的时候就需要用到ref属性,具体使用如下 var MyComponent = React.createClass({handleClick: function() {con ...

  3. react中使用构建缓存_如何在React中构建热图

    react中使用构建缓存 Heat maps are a great way of visualizing correlations among two data sets.  With colors ...

  4. react中使用构建缓存_通过构建海滩度假胜地网站,了解如何使用React,Contentful和Netlify...

    react中使用构建缓存 In this full course from John Smilga you will learn React by building a beach resort we ...

  5. 创建react应用程序_通过创建食谱应用程序来学习在React中使用API

    创建react应用程序 Learn how to use external APIs with React and React Router in a full tutorial from Hamza ...

  6. react中纯函数_如何在纯React中创建电子邮件芯片

    react中纯函数 by Andreas Remdt 由Andreas Remdt 如何在纯React中创建电子邮件芯片 (How to create email chips in pure Reac ...

  7. 如何在React中使用Typescript

    TypeScript can be very helpful to React developers. TypeScript对React开发人员可能非常有帮助. In this video, Ben ...

  8. react中使用scss_我如何将CSS模块和SCSS集成到我的React应用程序中

    react中使用scss by Max Goh 由Max Goh 我如何将CSS模块和SCSS集成到我的React应用程序中 (How I integrated CSS Modules with SC ...

  9. React 中动态的加载组件 ---loadable-components

    loadable-components 用于在react 中动态的加载组件 安装方法: npm i loadable-components 使用: 引入: 代码中使用: 希望对你有所帮助

最新文章

  1. WorldWind Java 版学习:10、服务器响应
  2. jittor和pytorch生成网络对比之wgan_gp
  3. Linux进程地址空间与进程内存布局详解,内核空间与用户空间
  4. 神奇的幻方2015提高组d1t1
  5. 利用CSS使元素在水平方向或水平,竖直同时居中
  6. web 前端 如何分享到instagram_面对前端的后端化趋势,2020该如何学习web前端?
  7. 桌面版docker操作_10分钟快速掌握Docker必备基础知识
  8. 利用ASP.NET一般处理程序动态生成Web图像
  9. oracle 建立一个游戏库,Power Designer怎么新建Oracle数据?建立Oracle数据教程分享
  10. Typescript tsconfig
  11. 【微信小程序】身份证拍照、上传并OCR识别
  12. Cocos2d-x 3.17.2 集成X5WebView内核方法,完美运行
  13. 简单计算器代码(含加减乘除取余5个操作)
  14. 手机号 MD5加密解密工具类
  15. 陀螺产业区块链第九季 | 如何用区块链搭建营销激励模型?
  16. 华三h3c系列交换机ACL实践
  17. 解决Chrome谷歌浏览器Adobe Flash Player 插件已被屏蔽的问题
  18. 【报告分享】2021年中国互联网保险消费者洞察报告-凯度元保清华大学国家金融研究院(附下载)
  19. Qt开发技术:QDBus介绍、编译与Demo
  20. c语言喜羊羊,青青草原101C位之争,喜羊羊vs懒羊羊

热门文章

  1. 读取unicode编码文件的乱码解决
  2. crfpp python
  3. 杭电1210 Eddy's 洗牌问题
  4. python编程(pyautogui库)
  5. oracle10g配置失败,求解决装oracle10g的时候EM配置失败问题
  6. php 9000 端口没起来,PHP无法监听9000端口问题/502错误解决办法
  7. python中的for语句可以在任意序列_python在循环内任意增加迭代器
  8. 《密码爆破漏洞详解》——黑客必修的入门操作( 建议收藏 )
  9. 什么是MongoDB
  10. WINDOWS SERVER 2008 R2 GHO 纯净版