2.7 WebSocket
WebSocket可以看作是HTTP协议的升级版,它同样是基于TCP协议的应用层协议,主要是为了弥补HTTP协议的无持久化和无状态等缺陷而诞生的。
WebSocket提供了客户端和服务器之间全双工的通信机制。
2.7.1 保持通话
当客户端发起一个请求时,客户端和服务器之间首先需要建立TCP连接,然后才能使用更高层的HTTP协议来解析数据。
HTTP是非持久化的,当用户发起一个request,服务器会随之返回一个response,那么这个HTTP连接就结束了,TCP连接也随之关闭。如果客户端想要继续访问服务器的内容,还需要重新建立TCP连接。对于连续的请求来说,这样会在TCP握手上浪费不少时间。
为了改进这一问题,浏览器在请求头里增加了Connection: Keep-Alive这一字段,当服务器收到带有这一头部的请求时会保持TCP连接不断开,同时也会在response中增加这一字段,这样浏览器和服务器之间只要建立一次TCP连接就可以进行多次HTTP通信。
在HTTP 1.1版本中,Connection: Keep-Alive被加入到标准之中,同时所有的连接都会被默认保持,除非手动指定Connection: Close。此外,为了避免无限制的长连接,服务器也会设置一个timeout属性,用来指定该连接最长可以保持时间。
keep-alive的最大优点在于其避免了多次的TCP握手带来的性能浪费;但还是有一些缺陷,其本质上还是使用HTTP进行通信,对于协议本身没有什么改进。
2.7.2 为什么要有WebSocket
假设我们要开发一个新闻类网站,该网站会一直将最新的新闻推送到页面上而不需要用户进行刷新操作,在WebSocket之前的HTTP协议中,服务器无法主动向客户端推送数据,对于这种情况有两种解决方案。
(1)客户端每隔几秒就发起Ajax请求,如果返回不为空,就将内容展示在页面上。
(2)使用长轮询,服务器收到客户端的请求后,如没有新的内容,就保持阻塞,当新内容产生后再发送response给客户端。
这两种做法的缺点都很明显。第一种可能客户端发送了很多请求才能得到一个新内容,第二种则是在服务器获得新内容前都无法关闭socket,会占用很多系统资源。
WebSocket可以实现浏览器与服务器的全双工通信,它和传统的jsonp、comet等解决方案不同,不必浏览器发送请求后再由服务器返回消息,而是可以由服务器主动发起向浏览器的数据传输。
WebSocket的请求头和HTTP很相似,下面是一个WebSocket header和服务器的response。
服务器返回以下消息:
在请求头中,Connection字段必须设置成Upgrade,表示客户端希望连接升级。
Upgrade字段必须设置WebSocket,表示希望升级到WebSocket协议。
Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。服务器会给这个随机的字符串再加上一个特殊字符串“ 258EAFA5-E 914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果作为Sec-WebSocket-Accept头的值返回给客户端。
“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”这一字符串是一个GUID,其本身并没有特别的含义,选择这个字符串的原因只是这个字符串不大可能被WebSocket之外的协议用到(协议本身描述如下:which is unlikely to be used by network endpoints that do not understand the WebSocket Protocol)。
Sec-WebSocket-Version表示支持的WebSocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当被弃用。
Origin字段是可选的,通常用来表示在浏览器中发起此WebSocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。
其他一些定义在HTTP协议中的字段,如Cookie等,也可以在WebSocket中使用。
2.7.3 WebSocket与Node
在NPM中有很多支持WebSocket的第三方模块,这里使用WS这一第三方模块。
下面是一个简单的例子。
代码2.19 JavaScript连接WebScoket
代码2.20 Node实现的WebSocket服务器
在Node里,比较出名的WebSocket模块还有Socket.IO,常被拿来做在线的聊天或者推送服务。读者有兴趣可以自行了解。