WebSocket 是双工的,他支持在客户端和服务器之间互相发送文本或二进制消息流,除此功能以外,它还提供了更为复杂的附加扩展:
连接协商和同源策略实施
与现有HTTP基础设施的互相操作性
面向消息的通信和高效的消息框架
这一点与Socket不同,Socket算是面向字节,他没有消息头、消息尾的概念。可以说Socket没有那么聪明
子协议协商和可扩展性
值得注意的一点是:WebSocket 不是 HTTP、XHR 或 SSE 的替代品,为了获得最佳性能,利用每种传输的优势至关重要。
这两种方案都是WebSocket的自定义方案,WS用于纯文本(即:明文)通讯,WSS用于加密管道通讯。
WebSocket比较好的一点就是:无需担心缓冲、解析和重构接收到的数据。
例:如果服务器发送 1 MB 的有效负载,客户端每次只能接收250 kb的有效荷载,那么他会接收四次,当接收完毕后onmessage才会在客户端调用应用程序的回调。
有效负载:我有个长度为5 MB的字节容器(即:一个int类型的数组),我要发送的消息转为字节数组后,长度只有1 MB,那么这1 MB称为有效负载,剩下的将是无效负载
有效载荷:客户端每次只能接收250 kb(即:一个长度为250*1024的字节数组),如果接收到了200 kb的数据,那么200有效载荷 50无效载荷
浏览器接收到一条新消息时,它会自动转换为基于文本数据的 DOMString 对象或二进制数据的 Blob 对象,作为客户端性能提升和优化的唯一方法就是,告诉浏览器将收到的二进制数据转换为ArrayBuffer 而不是Blob
var ws = new WebSocket('wss://example.com/socket');
ws.binaryType = "arraybuffer";
Blob:脱机存在磁盘中或单独存在内存中
Blob 对象表示不可变的原始数据的类文件对象,如果您不需要改动二进制数据(例如我只需要这么一个Blob文件对象),那么这是最好的选择。
ArrayBuffer:可能更有效将数据保存在内存中
如果您需要对二进制数据进行额外的处理,那么ArrayBuffer可能更合适。
ArrayBuffer 是一个结构化的,二进制数据的固定长度容器
WebSocket 连接成功后,客户端将是一个双向通讯管道,它允许通过同一个 TCP 连接在两个方向上传递消息,发送或接收 UTF-8 和二进制消息。
默认Send方法,接受一个 DOMString 对象,该对象在线路上被编码为 UTF-8,当然也可以用于二进制传输的 ArrayBuffer、ArrayBufferView 或 Blob 对象之一。
但是请注意,后一种二进制的方式只是为了方便API,在网络层面中,WebSocket的数据帧(即:发送的数据包),会通过单个字节位标记为二进制或文本,因此,如果应用程序想要使用其他类型的信息,那么双方必须约定一种新的机制来通讯该数据。
Send方法是异步的,将提供的数据由客户端排队发出,立即返回结果,这里的立即返回结果不代表你的信息已经发送完了,真正的发送完成是要监控当前浏览器的排队数据量
var ws = new WebSocket('wss://example.com/socket');
ws.onopen = function () {
// 当应用更新时触发
subscribeToApplicationUpdates(function(evt) {
// 如果待发送的字节数为0
if (ws.bufferedAmount == 0)
// 发送下一次请求
ws.send(evt.data);
});
};
所有 WebSocket 消息都按照它们在客户端排队的确切顺序进行传递!!!
大量的排队消息,或者单个大消息都将延迟其后面排队的消息的传递——队列头阻塞!
解决方案:
应用程序应该密切关注每种类型的消息如何以及何时在套接字上排队!
WebSocket 协议的默认消息格式只有两种,文本数据和二进制数据,以便客户端和服务端可以有效的对其进行编码,如果不属于这两种,消息内容将是不透明的,服务端和客户端将会不认识,导致无法解释其内容。
WebSocket 与 HTTP 或 XHR请求不同,它们通过Header传递额外的信息,而WebSocket没有这样的协议,因此如果想要获取额外的数据信息,那么可以通过下面的方式:
统一的JSON编码或自定义的二进制数据来通讯
如果想要传输不同格式的数据,那么可以通过约定消息头
文本和二进制消息混合使用
原始的WebSocket提供了子协议协商API来解决这个问题,最开始连接时,客户端可以告诉服务器他支持的协议,例:
var ws = new WebSocket('wss://example.com/socket',
['appProtocol', 'appProtocol-v2'])
如果子协议协商成功,则在 onopen处触发回调,应用程序可以查询WebSocket实例上的protocol属性来确定服务器选择的协议,如果协商不成功,即服务器不支持,则代表WebSocket协商是不完整的,将调用onerror回调。
WebSocket协议由两个高级组件组成:
WebSocket 协议是一个功能齐全的独立协议,可以在浏览器之外使用。话虽如此,它的主要应用程序是作为基于浏览器的应用程序的双向传输。
WebSocket 使用了一种自定义的二进制帧格式,它将每个应用程序消息拆分为一个或多个帧,将它们传输到目的地,重新组装它们,最后在收到整个消息后通知接收者。
帧
通信的最小单元,每一单元包含一个变长帧报头和一个可以承载全部或部分应用程序消息的有效载荷。
消息
映射到逻辑应用程序消息的一个完整的帧序列。
我们了解一下WebSocket的帧是怎么运行的?
帧是一个32位数据包(即:32-bit,一次处理4字节,1字节8位),我们来由浅入深一下:
比如a在ASCII编码中代表着97,那么97也就是所谓的字节,然后再通过toString(2)得到01100001,这是一个8 bit(即:8位)的位数据,所谓的帧就是这样的一堆二进制(也就是前面提到的8 bit)组成的。
现在我们来看一下服务器发送给客户端的原始数据,服务器发送给客户端的信息是两个简单的字符:aa
10000001 00000010 01100001 01100001
这是四字节,你可以尝试用后两个字节 01100001 01100001 去二进制转换字符串(但是在转换时您需要将它拼接起来,0110000101100001),你将得到信息aa
只是在目前的例子中他是占用了两个字节,其他情况下请参考数据内容
我们来看第一个字节(10000001)
1
FIN:表示该数据包是不是消息的最后一个数据包(也就是说,如果他不是1,那么表示数据包还没有传输完成)
000,分别代表RSV1、RSV2、RSV3,
这三位必须是0,除非与服务器协商了该扩展,如果这三个都不为0,那么服务器应该立即终止链接
0001
这四位代表操作码
- 0000:代表连续的帧
- 0001:代表文本帧
- 0010:代表二进制帧
- 0011-0111:保留的帧,一般碰不到
- 1000:代表连接要关闭
- 1001:表示ping
- 1010:表示pong
- 1011-1111:保留的帧
我们接下来拆分第二个字节(00000010)
0
掩码,如果该位为1,那么后面会有一个掩码秘钥,秘钥占4字节
0000010
这七位代表有效的载荷长度,转换为数字为:2,也就是对应的
aa的长度,在这里如果转换为的数字为0-125,那么这就是应用数据的有效载荷长度。如果为126,那么应该加上往后的扩展载荷长度,此情况下,扩展载荷长度为2个字节。
如果为127,那么扩展载荷长度为8个字节。
如果有扩展的长度,那么就需要将这些长度的位拼接起来,转换为一个数字,该数字为真正的有效载荷的长度。
0或4字节
该项为掩码秘钥,用于屏蔽有效负载数据,即加密,此加密在本文下方有解释。
有效载荷数据:X+Y
即扩展数据加应用数据
扩展数据
该数据应该是在创建连接(即:握手时)就应该协商好的,默认为0字节,除非协商了扩展,以及定义好了扩展的长度,或长度应该如何计算。
应用数据
在扩展数据后的帧将是应用数据
掩码是为了防止中介执行缓存中毒攻击,该攻击方式重点在于中介,此处的中介也就是一个代理服务器,假设他是一个负载均衡代理,那么他将负责转发流量,我们知道WebSocket和HTTP并不相同,WebSocket发送到是原始字节,这可以使其发送任何内容,比如模仿一个http请求:
soc.send(`
GET /script.js HTTP/1.1
HOST:恶意站点
`)
当这样的一个消息,发送至中介服务器时,中介服务器并不知道这是一个WebSocket的数据封包,它将会被认为是HTTP请求,这将转发到恶意站点,此时如果有相应的缓存设置,那么接下来,访问该安全站点 script.js 文件的无辜用户,将会获取到恶意站点的 script.js 文件。
而掩码将会将 WebSocket 发送出去的数据进行屏蔽(加密),屏蔽完的数据他将不是明文的,而是看上去像是乱码的数据。
至此,WebSocket的基本原理就是这样,以上全文均为本人个人理解,如果有误还请各位dai佬们指出。
接下来我会抽空将C#编写的简易WebSocketClient做成博客发出来。
第一次发博客,轻点喷,在此求求各位大佬了~
我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d
IntrductionLibwebsocketsisasimple-to-use,MIT-license,pureClibraryprovidingclientandserverforhttp/1,http/2,websockets,MQTTandotherprotocolsinasecurity-minded,lightweight,configurable,scalableandflexibleway.It’seasytobuildandcross-buildviacmakeandissuitablefortasksfromembeddedRTOSthroughmasscloudservi
我有带有gemwebsocket-rails0.7的Rails3.2应用程序。在开发机上,一切正常在生产环境中,我使用Nginx/1.6作为代理服务器,Unicorn作为http服务器。Thin用于独立模式(在https://github.com/websocket-rails/websocket-rails/wiki/Standalone-Server-Mode之后)。nginx配置:location/websocket{proxy_passhttp://localhost:3001/websocket;proxy_http_version1.1;proxy_set_headerUp
目录一、什么是Websocket二、WebSocket部分header介绍三、HTTPVSWebSocket四、什么时候使用WebSockets五、关于SockJS和STOMP一、什么是Websocket根据RFC6455标准,Websocket协议提供了一种标准化的方式在客户端和服务端之间通过TCP连接建立全双工、双向通信渠道。它是一种不同于HTTP的TCP协议,但是被设计为在HTTP基础上运行。Websocket交互始于HTTP请求,该请求会通过HTTPUpgrade请求头去升级请求,进而切换到Websocket协议。请求报文如下:GET/spring-websocket-portfoli
我开始使用websocket-rails,试图将旧的通知轮询系统(在Ruby2.1/Rails4.0上)转换为更现代的WS系统。我在独立模式下使用WebsocketRails,这是我的配置,基本上是默认配置:WebsocketRails.setupdo|config|config.standalone=trueend我还设置了一个在默认端口上运行的新Redis-这里似乎没有通信问题。在客户端,我添加了websocket-rails的JS,并在尝试打开连接和订阅channel时使用:@dispatcher=newWebSocketRails"localhost:3001/websocke
ActionCable在生产中不起作用。在开发中运行良好,但在生产中运行不佳。在Ubuntu14.04上使用Puma运行Nginx。我已经检查过redis-server已启动并正在运行。Rails-v5.0.0.1production.log:INFO--:StartedGET"/cable/"[non-WebSocket]for178.213.184.193at2016-11-2514:55:39+0100ERROR--:FailedtoupgradetoWebSocket(REQUEST_METHOD:GET,HTTP_CONNECTION:close,HTTP_UPGRADE:)
我是React的新手,我在组件结构和它们之间共享websocket方面遇到了一些问题。该应用程序由类别和产品组成。初始数据加载将通过Ajax请求完成,并且将使用websocket保持数据更新。我的组件层次结构如下所示:类别列表类别产品列表产品CategoriesList保存类别的状态,ProductsList保存类别中产品的状态。所以我想在CategoriesList和ProductsList中使用相同的websocket,但监听不同的websocket事件:category:updated和product:updated。如何在组件之间共享websocket以及初始化它的正确位置?由
我是Node.js或websocket的初学者。我有问题:我的HTML代码:test"usestrict";vargniazdo=newWebSocket('ws://localhost:3000');gniazdo.onopen=function(){console.log('Połączono');};gniazdo.onmessage=function(m){console.log(m.data);};我的Node.js代码:vario=require('socket.io')(3000);io.on('connection',function(socket){console.l
我玩了一个friend制作的游戏,并希望通过使用WebRTC和websockets在对等点之间发送按键数据来使其可以跨浏览器玩。但是,我在控制台中收到此错误:WebSocketconnectionto'ws://localhost:3000/'failed:Connectionclosedbeforereceivingahandshakeresponse我的服务器文件有以下几行:'usestrict';constexpress=require('express');constSocketServer=require('ws').Server;constpath=require('pat
为了能够在利用Socket.IO的RPC功能的同时发送二进制数据,我认为我可以同时使用Socket.IO和WS同一台服务器上的模块。我想知道是否可以使用同一个HTTP服务器,而不是打开完全独立的服务器来建立两个连接。是否可以同时为Socket.IO和WS仅使用一个通过http.createServer()创建的服务器?明确地说,我希望从客户端创建Socket.IO连接和常规WebSocket连接。以下代码在客户端创建协议(protocol)错误,大概是因为Socket.IO和WS都在尝试处理连接。varhttp=require('http');varserver=http.create