이 글은 JavaScript와 그 구성 요소를 살펴보는 시리즈의 다섯 번째 기사입니다. ㅋㅋㅋ . WebSocket과 HTTP/2의 빠른 비교. 마지막으로 네트워크 프로토콜을 선택하는 방법에 대한 몇 가지 팁을 공유합니다.
소개
요즘에는 풍부한 기능과 동적 UI를 갖춘 복잡한 웹 애플리케이션이 당연한 것으로 간주됩니다. 이는 놀라운 일이 아닙니다. 인터넷은 탄생 이후 많은 발전을 이루었습니다.HTTP를 "양방향" 상호 작용으로 만들기
서버가 클라이언트에 데이터를 "적극적으로" 보낼 수 있게 하는 기술은 꽤 오랫동안 사용되어 왔습니다. 예를 들어 "푸시" 및 "혜성"이 있습니다. 가장 일반적인 해킹 중 하나는 클라이언트에 데이터를 전송해야 한다는 착각을 서버에 제공하는 것입니다. 이를
긴 폴링이라고 합니다. 긴 폴링을 사용하면 클라이언트는 서버에 대한 HTTP 연결을 열고 응답이 전송될 때까지 열어 둡니다. 이는 서버에 보낼 새 데이터가 있을 때마다 응답으로 전송됩니다.
아주 간단한 긴 폴링 코드 조각이 어떻게 보이는지 살펴보세요.
(function poll(){ setTimeout(function(){ $.ajax({ url: 'https://api.example.com/endpoint', success: function(data) { // Do something with `data` // ... //Setup the next poll recursively poll(); }, dataType: 'json' }); }, 10000); })();
이것은 기본적으로 처음에 즉시 실행되고 서버 호출 사이에 10초 간격을 설정하는 자체 실행 함수입니다. 비동기 Ajax 호출 후, 콜백은 Ajax를 다시 호출합니다.
다른 기술에는 Flash 또는 XHR 다중 부분 요청 및 소위 html 파일이 포함됩니다. 그러나 이러한 작업 공간에는 모두 동일한 문제가 있습니다. 모두 HTTP 오버헤드가 있으므로 지연 시간이 짧은 애플리케이션에 적합하지 않습니다. 브라우저 내 멀티플레이어 1인칭 슈팅 게임이나 실시간 구성 요소가 포함된 기타 온라인 게임을 생각해 보세요.
WebSocket 소개WebSocket 사양은 웹 브라우저와 서버 간의 "소켓" 연결을 설정하기 위한 API를 정의합니다. 간단히 말해서, 클라이언트와 서버 사이에는 오랫동안 지속되는 연결이 있으며, 양 당사자는 언제든지 데이터 전송을 시작할 수 있습니다.
클라이언트는 WebSockethandshake
프로세스를 통해 WebSocket 연결을 설정합니다. 프로세스는 클라이언트가 WebSocket 연결을 설정하려고 함을 서버에 알리는 업그레이드 헤더가 포함된 일반 HTTP 요청을 서버에 보내는 것으로 시작됩니다. 클라이언트는 다음과 같이 WebSocket 연결을 설정합니다.// Create a new WebSocket with an encrypted connection. var socket = new WebSocket('ws://websocket.example.com')WebSocket URL은 ws 구성표를 사용합니다. HTTPS와 동등한 보안 WebSocket 연결을 위한 wss도 있습니다. 이 솔루션은 websocket.example.com의 WebSocket 연결을 여는 시작일 뿐입니다. 다음은 초기 요청 헤더의 간단한 예입니다.
서버가 WebSocket 프로토콜을 지원하는 경우 업그레이드에 동의하고 응답의 업그레이드 헤더를 통해 이를 전달합니다.
Node.js 구현:연결이 설정된 후 서버는 헤더의 콘텐츠를 업데이트하여 응답합니다.
연결이 설정되면 클라이언트 WebSocket에서 open 이벤트가 트리거됩니다. 예:
var socket = new WebSocket('ws://websocket.example.com'); // Show a connected message when the WebSocket is opened. socket.onopen = function(event) { console.log('WebSocket is connected.'); };
이제 핸드셰이크가 완료되었으므로 초기 HTTP 연결은 동일한 기본 TCP/IP 연결을 사용하는 WebSocket 연결로 대체됩니다. 이 시점에서 양측은 데이터 전송을 시작할 수 있습니다.
使用 WebSockets,可以传输任意数量的数据,而不会产生与传统 HTTP 请求相关的开销。数据作为消息通过 WebSocket 传输,每个消息由一个或多个帧组成,其中包含正在发送的数据(有效负载)。为了确保消息在到达客户端时能够正确地进行重构,每一帧都以负载的4-12字节数据为前缀, 使用这种基于帧的消息传递系统有助于减少传输的非有效负载数据量,从而大大的减少延迟。
注意:值得注意的是,只有在接收到所有帧并重构了原始消息负载之后,客户机才会收到关于新消息的通知。
WebSocket URLs
之前简要提到过 WebSockets 引入了一个新的URL方案。实际上,他们引入了两个新的方案:ws:// 和wss://。
url 具有特定方案的语法。WebSocket url 的特殊之处在于它们不支持锚点(#sample_anchor)。
同样的规则适用于 WebSocket 风格的url和 HTTP 风格的 url。ws 是未加密的,默认端口为80,而 wss 需要TLS加密,默认端口为 443。
帧协议
更深入地了解帧协议,这是 RFC 为我们提供的:
在RFC 指定的 WebSocket 版本中,每个包前面只有一个报头。然而,这是一个相当复杂的报头。以下是它的构建模块:
FIN
:1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧,Firefox 在 32K 之后创建了第二个帧。RSV1,RSV2,RSV3
:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。Opcode
:4bit,解释 Payload 数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:
Mask
:1bit,掩码,定义payload数据是否进行了掩码处理,如果是1表示进行了掩码处理。Masking-key
:域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。Payload_len
:7位,7 + 16位,7+64位,payload数据的长度,如果是0-125,就是真实的payload长度,如果是126,那么接着后面的2个字节对应的16位无符号整数就是payload数据长度;如果是127,那么接着后面的8个字节对应的64位无符号整数就是payload数据的长度。Masking-key
:0到4字节,如果MASK位设为1则有4个字节的掩码解密密钥,否则就没有。Payload data
:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。为什么 WebSocket 是基于帧而不是基于流?我不知道,就像你一样,我很想了解更多,所以如果你有想法,请随时在下面的回复中添加评论和资源。另外,关于这个主题的讨论可以在 HackerNews 上找到。
帧数据
如上所述,数据可以被分割成多个帧。 传输数据的第一帧有一个操作码,表示正在传输什么类型的数据。 这是必要的,因为 JavaScript 在开始规范时几乎不存在对二进制数据的支持。 0x01 表示 utf-8 编码的文本数据,0x02 是二进制数据。大多数人会发送 JSON ,在这种情况下,你可能要选择文本操作码。 当你发送二进制数据时,它将在浏览器特定的 Blob 中表示。
通过 WebSocket 发送数据的API非常简单:
var socket = new WebSocket('ws://websocket.example.com'); socket.onopen = function(event) { socket.send('Some message'); // Sends data to server. };
当 WebSocket 接收数据时(在客户端),会触发一个消息事件。此事件包括一个名为data的属性,可用于访问消息的内容。
// Handle messages sent by the server. socket.onmessage = function(event) { var message = event.data; console.log(message); };
在Chrome开发工具:可以很容易地观察 WebSocket 连接中每个帧中的数据:
消息分片
有效载荷数据可以分成多个单独的帧。接收端应该对它们进行缓冲,直到设置好 fin
位。因此,可以将字符串“Hello World”发送到11个包中,每个包的长度为6(报头长度)+ 1字节。控件包不允许分片。但是,规范希望能够处理交错的控制帧。这是TCP包以任意顺序到达的情况。
连接帧的逻辑大致如下:
分片目的是发送长度未知的消息。如果不分片发送,即一帧,就需要缓存整个消息,计算其长度,构建frame并发送;使用分片的话,可使用一个大小合适的buffer,用消息内容填充buffer,填满即发送出去。
什么是跳动检测?
主要目的是保障客户端 websocket 与服务端连接状态,该程序有心跳检测及自动重连机制,当网络断开或者后端服务问题造成客户端websocket断开,程序会自动尝试重新连接直到再次连接成功。
在使用原生websocket的时候,如果设备网络断开,不会触发任何函数,前端程序无法得知当前连接已经断开。这个时候如果调用 websocket.send 方法,浏览器就会发现消息发不出去,便会立刻或者一定短时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose 函数。
后端 websocket 服务也可能出现异常,连接断开后前端也并没有收到通知,因此需要前端定时发送心跳消息 ping,后端收到 ping 类型的消息,立马返回 pong 消息,告知前端连接正常。如果一定时间没收到pong消息,就说明连接不正常,前端便会执行重连。
为了解决以上两个问题,以前端作为主动方,定时发送 ping 消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。
错误处理
以通过监听 error 事件来处理所有错误:
var socket = new WebSocket('ws://websocket.example.com'); // Handle any error that occurs. socket.onerror = function(error) { console.log('WebSocket Error: ' + error); };
关闭连接
要关闭连接,客户机或服务器都应该发送包含操作码0x8
的数据的控制帧。当接收到这样一个帧时,另一个对等点发送一个关闭帧作为响应,然后第一个对等点关闭连接,关闭连接后接收到的任何其他数据都将被丢弃:
// Close if the connection is open. if (socket.readyState === WebSocket.OPEN) { socket.close(); }
另外,为了在完成关闭之后执行其他清理,可以将事件侦听器附加到关闭事件:
// Do necessary clean up. socket.onclose = function(event) { console.log('Disconnected from WebSocket.'); };
服务器必须监听关闭事件以便在需要时处理它:
connection.on('close', function(reasonCode, description) { // The connection is getting closed. });
WebSockets和HTTP/2 比较
虽然HTTP/2提供了很多功能,但它并没有完全满足对现有推送/流技术的需求。
关于 HTTP/2 的第一个重要的事情是它并不能替代所有的 HTTP 。verb、状态码和大部分头信息将保持与目前版本一致。HTTP/2 是意在提升数据在线路上传输的效率。
比较HTTP/2和WebSocket,可以看到很多相似之处:
正如我们在上面看到的,HTTP/2引入了 Server Push,它使服务器能够主动地将资源发送到客户机缓存。但是,它不允许将数据下推到客户机应用程序本身,服务器推送只由浏览器处理,不会在应用程序代码中弹出,这意味着应用程序没有API来获取这些事件的通知。
这就是服务器发送事件(SSE)变得非常有用的地方。SSE 是一种机制,它允许服务器在建立客户机-服务器连接之后异步地将数据推送到客户机。然后,只要有新的“数据块”可用,服务器就可以决定发送数据。它可以看作是单向发布-订阅模式。它还提供了一个名为 EventSource API 的标准JavaScript,作为W3C HTML5标准的一部分,在大多数现代浏览器中实现。不支持 EventSource API 的浏览器可以轻松地使用 polyfilled 方案来解决。
由于 SSE 基于 HTTP ,因此它与 HTTP/2 非常合适,可以结合使用以实现最佳效果:HTTP/2 处理基于多路复用流的高效传输层,SSE 将 API 提供给应用以启用数据推送。
为了理解 Streams 和 Multiplexing 是什么,首先看一下`IETF
定义:“stream”是在HTTP/2 连接中客户机和服务器之间交换的独立的、双向的帧序列。它的一个主要特征是,一个HTTP/2 连接可以包含多个并发打开的流,任何一个端点都可以从多个流中交错帧。
SSE는 HTTP를 기반으로 합니다. 즉, HTTP/2에서는 여러 SSE 스트림을 단일 TCP 연결에 인터리브할 수 있을 뿐만 아니라 여러 SSE 스트림(서버-클라이언트 푸시) 및 여러 클라이언트 요청(클라이언트 서버로). HTTP/2 및 SSE 덕분에 이제 순수한 HTTP 양방향 연결과 애플리케이션 코드가 서버 푸시 서비스에 등록할 수 있는 간단한 API가 있습니다. SSE와 WebSocket을 비교할 때 양방향 기능이 없다는 점은 종종 주요 단점으로 언급됩니다. HTTP/2에서는 더 이상 그렇지 않습니다. 이를 통해 WebSocket을 건너뛰고 HTTP 기반 신호 메커니즘을 사용할 수 있습니다.
WebSocket과 HTTP/2 중에서 선택하는 방법은 무엇입니까?
WebSocket은 HTTP/2 + SSE의 세계에서 살아남을 것입니다. 주로 WebSocket은 이미 잘 확립되어 있고 매우 특정한 용도를 갖고 있는 기술이기 때문입니다. 헤더와 같은 오버헤드가 적은 양방향 기능을 위해 구축되었기 때문에 HTTP/2에 비해 장점이 있습니다.
연결 양쪽 끝에서 많은 메시지가 필요한 대규모 멀티플레이어 온라인 게임을 구축한다고 가정해 보겠습니다. 이 경우 WebSocket의 성능이 훨씬 향상됩니다.
일반적으로 클라이언트와 서버 간의 거의 실시간 연결이 필요한 진정한 낮은 대기 시간이 필요할 때마다 WebSocket을 사용하세요. 이를 위해서는 서버 측 애플리케이션 구축 방법을 다시 생각하고 대기열에 있는 이벤트에 초점을 맞춰야 할 수도 있습니다. 등 기술적으로.
실시간 시장 뉴스, 시장 데이터, 채팅 애플리케이션 등을 표시해야 하는 솔루션을 사용하세요. HTTP/2 + SSE를 사용하면 효율적인 양방향 통신 채널을 제공하는 동시에 HTTP 영역:
위 내용은 SSE를 사용하는 웹소켓 및 HTTP/2에 대한 JavaScript 심층 분석 + 올바른 경로를 선택하는 방법!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!