当前位置: 首页 > 后端技术 > Node.js

深入了解WebSockets

时间:2023-04-03 14:40:45 Node.js

WebSockets简介在2008年年中,开发人员MichaelCarter和IanHickson特别敏锐地意识到Comet在实现任何真正强大的东西时所带来的痛苦和局限性。通过在IRC和W3C邮件列表上的合作,他们制定了一项计划,为网络上的现代实时双向通信引入新标准,从而创造了“WebSocket”这个名称。这个想法进入了W3CHTML标准草案,不久之后,MichaelCarter写了一篇文章向Comet社区介绍WebSockets。2010年,GoogleChrome4成为第一个全面支持WebSockets的浏览器,其他浏览器供应商在接下来的几年里纷纷效仿。2011年,RFC6455-WebSocket协议-发布到IETF网站。如今,所有主流浏览器都完全支持WebSockets,甚至包括InternetExplorer10和11。此外,iOS和Android上的浏览??器自2013年起就支持WebSockets,这意味着总的来说,WebSocket支持的现代环境非常健康。大多数“物联网”或IoT也在某些版本的Android上运行,因此截至2018年,其他类型设备上的WebSocket支持也相当普遍。那么WebSocket到底是什么?简而言之,WebSockets是建立在设备的TCP/IP堆栈之上的传输层。目的是为Web应用程序开发人员提供一个在本质上尽可能接近原始TCP的通信层,同时添加一些抽象以消除一些差异。它们还解决了这样一个事实,即网络必须考虑额外的安全注意事项,以保护消费者和服务提供商。您可能听说过WebSockets被称为“传输”和“协议”。前者更准确,因为虽然它们是一种协议,必须遵循一组严格的规则来建立通信并包含正在传输的数据,但该标准并未对实际数据有效载荷的构建方式做出任何规定。事实上,规范的一部分包括客户端和服务器就传输数据格式化和解释协议达成一致的规范。该标准将这些称为“子协议”,以避免术语含糊不清的问题。子协议的例子有JSON、XML、MQTT、WAMP等。它们不仅确保了数据的结构,还确保了通信必须如何开始、继续和最终终止。只要双方明白协议的内容,什么事情都可能发生。WebSocket只提供传输层,通过它可以实现这个消息传递过程,这就是为什么大多数常见的子协议并不是基于WebSocket的通信所独有的。关于身份验证和授权的快速说明将WebSockets视为构建在TCP/IP之上的薄层,任何超出基本握手和消息框架规范的内容都需要在每个应用程序或每个库的基础上进行处理。引用RFC:该协议没有规定服务器在WebSocket握手期间可以对客户端进行身份验证的任何特定方式。WebSocket服务器可以使用通用HTTP服务器可用的任何客户端身份验证机制,例如cookie、HTTP身份验证或TLS身份验证。总之,你仍然可以使用基于HTTP的认证方式,或者使用像MQTT或WAMP这样的子协议,它们都提供了认证和授权的方式。定义带有HTTP连接的WebSocket标准时的早期考虑之一是确保它“与网络良好配合”。这意味着要认识到Web通常是使用URL而不是IP地址和端口号来寻址的,并且WebSocket连接应该能够与任何其他类型的基于HTTP的Web请求进行相同的初始握手。下面是一个简单的HTTPGET请求中发生的事情。假设在http://www.example.com/index....无需深入了解HTTP协议本身,知道请求必须以所谓的Request-Line开始,然后是一系列密钥就足够了-value对标题行,每个标题行都告诉服务器一些关于什么的信息。期望在后续请求有效负载中遵循标头数据,以及它可以从客户端期望的关于它理解的响应类型的信息。请求中的第一个标记是HTTP方法,它告诉服务器客户端针对引用的URL尝试的操作类型。当客户端简单地请求服务器向其提供指定URL引用的资源的副本时,将使用GET方法。根据HTTPRFC格式化的请求标头的系统示例如下所示:GET/index.htmlHTTP/1.1Host:www.example.com在收到请求标头后,服务器然后格式化以状态行开头的响应标头标头,后跟一组键值标头对,为客户端提供来自服务器的关于服务器请求的补充信息。回复。“状态行”告诉客户端HTTP状态代码(如果没有问题,通常为200),并提供简短的“原因”文本描述来解释状态代码。接下来出现键值标头对,然后是请求的实际数据(除非状态代码指示由于某种原因无法满足请求)。HTTP/1.1200OKDate:Wed,1Aug201816:03:29GMTContent-Length:291Content-Type:text/html(additionalheaders...)(responsepayloadcontinueshere...)然后你可能会问,这是什么它与WebSockets有关系吗?放弃HTTP以获得更合适的东西发出HTTP请求和接收响应时涉及的实际双向网络通信是通过活动的TCP/IP套接字发生的。在浏览器中请求的网址通过全球DNS系统映射到IP地址,HTTP请求的默认端口为80。这意味着虽然在浏览器中输入了网址,但实际通信是通过TCP/IP进行的,使用类似123.11.85.9:80IP地址和端口的组合。我们现在知道WebSockets也是建立在TCP堆栈之上的,这意味着我们所需要的只是一种让客户端和服务器相互同意保持套接字连接打开并重新使用它进行持续通信的方法。如果他们这样做,他们就可以发送和接收二进制数据。要开始为WebSocket通信重新调整TCP套接字,客户端可以包含专门为此类用例发明的标准请求标头:GET/index.htmlHTTP/1.1Host:www.example.comConnection:UpgradeUpgrade:websocketConnectionheader告诉服务器客户端希望协商改变套接字的使用方式。伴随的Upgrade值表示应该更改当前在TCP上使用的传输协议。现在服务器知道客户端想要通过活动的TCP套接字升级它当前使用的协议,服务器知道寻找相应的升级标头,它将告诉它客户端想要在剩余的生命周期中使用哪种传输协议的连接。一旦服务器将websocket视为Upgrade标头的值,它就知道WebSocket握手过程已经开始。请注意,如果您想了解本文所涵盖内容的更多详细信息,握手过程(以及其他所有内容)在RFC6455中有所概述。避免有趣的麻烦除了上面描述的内容之外,WebSocket握手的第一部分涉及证明这实际上是一个正确的WebSocket升级握手,并且该过程没有被客户端或可能被某种中间欺骗所规避或模拟.中间有一个代理服务器。当启动对WebSocket连接的升级时,客户端必须包含一个Sec-WebSocket-Key标头,该标头具有对该客户端唯一的值。这是一个示例:Sec-WebSocket-Key:BOq0IliaPZlnbMHEBYtdjmKIL38=如果您使用现代浏览器中可用的WebSocket类,上述内容将自动处理。您只需在服务器端查找它并生成响应。响应时,服务器必须将特殊的GUID值258EAFA5-E914-47DA-95CA-C5AB0DC85B11附加到密钥,生成结果字符串的SHA-1哈希,然后将其包含为Sec的base-64编码值。它在响应中包含WebSocket-Accept标头:Sec-WebSocket-Accept:5fXT1W3UfPusBQv/h6c4hnwTJzk=在Node.jsWebSocket服务器中,我们可以编写一个函数来生成此值,如下所示:constcrypto=require('crypto');functiongenerateAcceptValue(acceptKey){returncrypto.createHash('sha1').update(acceptKey+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11','binary').digest('base64');}然后我们只需要调用此函数,将Sec-WebSocket-Key头的值作为参数传递,并在发送响应时将函数返回值设置为Sec-WebSocket-Accept头的值。要完成握手,请将适当的HTTP响应标头写入客户端套接字。一个简单的响应如下所示:HTTP/1.1101WebSocketProtocolHandshakeUpgrade:WebSocketConnection:UpgradeSec-WebSocket-Accept:m9raz0Lr21hfqAitCxWigVwhppA=到目前为止,我们还没有完成握手——还有很多事情需要考虑。子协议-统一语言客户端和服务器通常需要就它们自己如何格式化、解释和组织数据的兼容策略达成一致,无论是在给定消息内还是在从一条消息到下一条消息的一段时间内。这就是子协议(前面提到的)的用武之地。如果客户端知道它可以处理一个或多个特定的应用程序级协议(例如WAMP、MQTT等),它可以包括它理解的协议列表。发出初始HTTP请求。如果是这样,服务器需要选择其中一种协议并将其包含在响应标头中,否则握手将失败并终止连接。子协议请求标头示例:Sec-WebSocket-Protocol:mqtt,示例wamp服务器响应发送的Reciprocal标头:Sec-WebSocket-Protocol:wamp注意,服务器必须从客户端提供的列表中选择一个协议。选择多个将意味着服务器无法可靠或一致地解释后续WebSocket消息中的数据。例如,如果服务器选择了json-ld和json-schema。两者都是建立在JSON标准之上的数据格式,在许多边缘情况下,一种可能被解释为另一种,从而在处理数据时导致意外错误。虽然不可否认本身不是消息传递协议,但该示例仍然适用。当客户端和服务器都实现为从一开始就使用通用消息传递协议时,Sec-WebSocket-Protocol标头可以从初始请求中省略,在这种情况下,服务器可以忽略此步骤。子协议协商在实现公共服务、基础设施和工具时最有用,因为一旦建立WebSocket连接,客户端和服务器无法保证彼此理解。通用协议的标准化名称应该在WebSocket子协议名称的IANA注册中心注册,截至撰写本文时,已经注册了36个名称,包括soap、xmpp、wamp、mqtt等。尽管注册表是将子协议名称映射到它们的解释的规范来源,但唯一严格的要求是客户端和服务器就他们共同选择的子协议的实际含义达成一致,无论它是否出现在IANA注册表中。请注意,如果客户端请求子协议但未提供服务器可以支持的任何内容,则服务器必须发送失败响应并关闭连接。还有一个用于WebSocket扩展的标头,它定义了如何对数据有效负载进行编码和构建,但在撰写本文时,只有一种标准化的扩展类型提供了WebSocket,相当于消息中的gzip压缩。缩放可能发挥作用的另一个例子是多路复用——使用单个套接字交错多个并发通信流。WebSocket扩展是一个有点高级的主题,超出了本文的范围。现在,知道它们是什么以及它们如何融入画面就足够了。客户端——在浏览器中使用WebSocketsWebSocketAPI是在WHATWGHTMLLivingStandard中定义的,实际上使用起来非常简单。构造一个WebSocket需要一行代码:constws=newWebSocket('ws://example.org');请注意,对于ws,您通常拥有http方案。也可以选择使用wss,一般是https。这些协议与WebSocket规范一起引入以表示HTTP连接,包括升级连接以使用WebSockets的请求。创建一个WebSocket对象本身并没有做太多事情。连接是异步建立的,所以你需要在发送任何消息之前监听握手是否完成,并且还包括一个监听从服务器接收的消息的监听器:ws.addEventListener('open',()=>{//Send给WebSocket服务器的消息ws.send('Hello!');});ws.addEventListener('message',event=>{//`event`对象是一个典型的DOM事件对象,//服务器发送的消息数据存储在`data`属性中console.log('Received:',事件数据);});还有错误和关机事件。当连接终止时,WebSockets不会自动恢复——这是你需要自己实现的东西,也是许多客户端库存在的原因之一。虽然WebSocket类简单易用,但它实际上只是一个基本构建块。必须单独实现对不同子协议或消息通道等附加功能的支持。生成和解析WebSocket消息帧一旦握手响应被发送到客户端,客户端和服务器就可以开始使用他们选择的子协议(如果有的话)进行通信。WebSocket消息在称为“帧”的包中传递,这些包以消息标头开始并以“有效负载”结束-此帧的消息数据。大消息可能会将数据拆分为多个帧,在这种情况下,您需要跟踪到目前为止已收到的内容,并在数据全部到达后对数据进行分组。翻译的很乱,希望对大家有点帮助。创建了一个程序员交流信息的微信群。如果大家进群交流IT技术。