最近研究了下WebSocket,总结下目前对WebSocket的认知。本文不是基于WebSocket展开的一个从0到1的详细介绍。如果你从来没有了解过WebScoket,建议可以先搜一些介绍WebSocket的文章,这类文章还是挺多的,我就不再赘述了。
下面的内容是基于你对WebSocket有基本了解后展开的几个小的知识点:
背景:连接WebSocket的时候,发现WebSocket刚连接上没过多久就断开了,为了保持长时间的连接,就想到了ping/pong协议。
问题:
通过WebSocket协议,发现ping/pong确实是一种特殊的帧类型:
The Ping frame contains an opcode of 0x9.
The Pong frame contains an opcode of 0xA.
那么,上面所说的opcode又是什么东西呢?讲opcode就得说到帧数据格式:
通过上图可以发现,除了最后面的Payload Data,也就是我们要发送的数据之外,还会有一些其他信息。我觉得可以类比http请求的请求头部分。上图中第5-8位表示的就是opcode的内容。其余字段的含义可以参考上述WebSocket规范,或者搜WebSocket协议数据帧格式,这类博客还是挺多的。
拿nodeJS举个例子:
在浏览器端发起WebSocket的时候,会发送一个http请求,注意请求头里面的Upgrade字段,意思就是我要升级到websocket连接:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
此时,nodeJS就可以监听upgrade事件,去做拒绝或者升级操作,注意下这个事件里面有个参数socket:
socket: <stream.Duplex> Network socket between the server and client
socket有一个write方法,该方法是可以用来写帧数据的,也就是上面帧格式里面的全部数据,而不仅仅是Payload Data。
ws仓库就是使用了socket的write方法发送了根据WebSocket协议定义的ping/pong,部分关键代码如下:
doPing(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09, // ping opcode
mask,
readOnly
}),
cb
);
}
doPong(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a, // pong opcode
mask,
readOnly
}),
cb
);
}
sendFrame(list, cb) {
if (list.length === 2) {
this._socket.cork();
this._socket.write(list[0]);
this._socket.write(list[1], cb);
this._socket.uncork();
} else {
this._socket.write(list[0], cb);
}
}
所以,nodeJS是可以实现WebSocket协议定义的ping/pong帧的。原因是我们可以拿到socket对象,并且该对象提供了可以发送完整帧数据的方法。那么浏览器端呢?
浏览器提供了原生的WebSocket构造函数用来创建一个WebSocket实例,该实例只提供了一个send方法,并且该send方法只能用来发送上述协议中Payload Data的内容,浏览器会根据send的参数自动生成一个完整的帧数据。所以,在浏览器端是没法控制除了Payload Data之外的帧内容的,也就是无法自定义opcode。所以,也就实现不了WebSocket规范定义的ping/pong协议。
此时,我们就可以把ping/pong当成一种用来解决特定问题的设计模式。既然我们只能自定义Payload Data的内容,那么我们可以简单的在Payload Data里面添加一个字段用于区分是ping/pong帧,还是普通的数据帧,比如type。当type字段是ping/pong的时候表明是ping/pong帧,如果是其他字段才是普通的数据帧。
当断网的时候,连接WebSocket会发现浏览器控制台会log一个错误信息:
WebSocket connection to 'ws://...' failed: Error in connection establishment: net::ERR_INTERNET_DISCONNECTED
原先的开发经验是,控制台如果有报错的话,肯定是代码某个地方有错误,并且没有被我们的代码捕获到,所以就会在控制台抛出,如果使用了try catch 或者全局的window.onerror捕获到了错误信息,就不会在控制台打印了。所以,我就尝试了上述方法,发现捕捉不到,还是会在控制台log。
另外,WebSocket提供了两个事件,onerror和onclose。当发生上述错误信息的时候,onerror和onclose是会被调用的。但是,此时控制台还是会有上述报错信息。
经过一番查找,发现无法阻止上述错误信息显示在控制台。
那么,为什么浏览器会设计这样的行为呢?猜测原因如下:
上面说到通过onerror和onclose事件是可以捕捉到WebSocket创建失败的,但是,查看这两个事件的参数,我们只能从中找到一个code是1006的属性,输出在控制台的错误信息ERR_INTERNET_DISCONNECTED在参数里面找不到。接着,看一下code1006相关的东西:
User agents must not convey any failure information to scripts in a way that would allow a script to distinguish the following situations:
* A server whose host name could not be resolved.
* A server to which packets could not successfully be routed.
* A server that refused the connection on the specified port.
* A server that failed to correctly perform a TLS handshake (e.g., the server certificate can't be verified).
* A server that did not complete the opening handshake (e.g. because it was not a WebSocket server).
* A WebSocket server that sent a correct opening handshake, but that specified options that caused the client to drop the connection (e.g. the server specified a subprotocol that the client did not offer).
* A WebSocket server that abruptly closed the connection after successfully completing the opening handshake.
In all of these cases, the the WebSocket connection close code would be 1006, as required by WebSocket Protocol.
Allowing a script to distinguish these cases would allow a script to probe the user's local network in preparation for an attack.
从上述规范可以看到,规范是禁止浏览器向脚本传递下述造成WebSocket连接失败的具体原因的,只允许向脚本传递一个1006的code码,否则,用户就可以探测到局部网的信息,进而发起攻击。举个例子,上面那种断网的情况,脚本中只能得到1006的状态码,比如下面这种报错
Error in connection establishment: net::ERR_CONNECTION_REFUSED
也只能从onerror中获得一个1006的code码。
所以,作为开发人员,浏览器要怎么在告诉我们具体的错误信息的同时又阻止有可能发生的攻击呢?答案就是在控制台把具体的错误信息log出来。
websocket是实现浏览器和web服务器沟通的一个好方法。一个比较好的使用方法就是在web网页上使用websocket,然后再起一个webdocketd服务器。
HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
WebSocket是基于TCP的一种新的网络协议,并在2011年被IETF定为标准的全双工通信协议,它实现了客户端与服务器全双工通信。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
Web Sockets定义了一种在通过一个单一的 socket 在网络上进行全双工通讯的通道。它不仅仅是传统的 HTTP 通讯的一个增量的提高,尤其对于实时、事件驱动的应用来说是一个飞跃。
WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)
在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。这样会有:服务器会继续向客户端发送多余的链接,并且这些数据还会丢失
在建立web socket通信后,发送数据时,出现下图所示现象:要明白这个问题产生的原因,就需要了解websocket的几个状态。通常在实例化一个websocket对象之后,客户端就会与服务器进行连接
在 websocket 出现之前,为了实现 web 端的实时通信,通常采用的是 Ajax 轮询技术,(轮询是在特定的时间间隔内,由浏览器向服务器发送 HTTP 请求,再由服务器返回最新的数据)
websocket服务端往往需要和服务层打交道,因此需要将服务层的一些bean注入到websocket实现类中使用,但是呢,websocket实现类虽然顶部加上了@Component注解,依然无法通过@Resource和@Autowire注入spring容器管理下的bean
以下 API 用于创建 WebSocket 对象。以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。以下是 WebSocket 对象的属性。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!