初探WebSocket
node websocket socket.io
我们平常开发的大部分web页面都是主动‘拉’的形式,如果需要更新页面内容,则需要“刷新”一个,但Slack工具却能主动收到信息,好像服务端能主动给客户端推送信息,请研究一下这是怎么实现的。
WebSocket
websocket是HTML5中新引进的一种 协议
,它是一种协议就像(HTTP,FTP在tcp/ip协议栈中属于应用层)而不是简单的一个函数。它本身及基于TCP协议的一种新的协议。
WebSocket的产生
websocket是基于web的实时性而产生的,说到这里就不得不要追溯一下web的历史了,在2005年(也就是ajax还没诞生)以前,我们如果想要在一个页面显示显示不同的内容,或者说页面内跳转,只能是通过点击然后路由跳转,在ajax诞生之后,网页开始变得动态了。但是所有的HTTP通信还都是由客户端控制的,这就要需要长连接,定期轮询或者长轮询,来和服务器沟通来更新数据。
WebSocket之前的服务器“推”的技术
- 定期轮询(ajax轮询):浏览器在特定的时间给服务器发送请求,查看服务器是否有信息数据。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。 - 长轮询:其实和上面的原理差不多,是对ajax轮询进行了改进和提高。客户端和服务端建立连接之后,一直保持通信(阻塞模式),如果服务器没有新消息就一直保持通信,直到服务器有新的消息,然后返回给客户端,客户端与服务器断开连接,此时客户端可以继续和服务器进行连接。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:旧的 WebQQ、Hi网页版、Facebook IM。 - 流控制:通常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务 器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。比如在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
优点:消息即时到达,不发无用请求;管理起来也相对方便。
缺点:服务器维护一个长连接会增加开销。
实例:Gmail聊天 - Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
优点:实现真正的即时通信,而不是伪即时。
缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
实例:网络互动游戏。
HTTP1.1和长链接
以上几种服务器“推”的技术中:长轮询和流控制其实都是基于长链接来实现的,也就是 http1.1
中所谓的 keep-alive
。在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。
HTTP是无状态的,也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话
HTTP1.1和HTTP1.0相比较而言,最大的区别就是HTTP1.1默认支持持久连接(最新的 http1.0 可以显示的指定 keep-alive),但还是无状态的,或者说是不可以信任的。
在向客户发送所请求文件的同时,服务器并没有存储关于该客户的任何状态信息。即便某个客户在几秒钟内再次请求同一个对象,服务器也不会响应说:自己刚刚给它发送了这个对象。相反,服务器重新发送这个对象,因为它已经彻底忘记早先做过什么。既然HTTP服务器不维护客户的状态信息,我们于是 说HTTP是一个无状态的协议(stateless protocol)。
基于http协议的长连接减少了请求,减少了建立连接的时间,但是每次交互都是由客户端发起的,客户端发送消息,服务端才能返回客户端消息。因为客户端也不知道服务端什么时候会把结果准备好,所以客户端的很多请求是多余的,仅是维持一个心跳,浪费了带宽。
WebSocket
WebSocket简介
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。
关于HTML5的故事很多人都是知道的,w3c放弃了HTML,然后有一群人(也有说是这些人供职的公司,不过官方的文档上是说的个人)创立了WHATWG组织来推动HTML语言的继续发展,同时,他们还发展了很多关于Web的技术标准,这些标准不断地被官方所接受。WebSocket就属于WHATWG发布的Web Application的一部分(即HTML5)的产物。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
其中 Upgrade: websocket Connection: Upgrade
告诉服务器我们发起的是一个 WebSocket
请求。Sec-WebSocket-Key
是一个 Base64encode
的值,这个是浏览器随机生成的,验证服务器是不是真的是Websocket助理。
然后,Sec_WebSocket-Protocol
是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。
最后,Sec-WebSocket-Version
是告诉服务器所使用的WebsocketDraft(协议版本)。
HTML5 Web Socket API
详细接口文档: MDN WebSocket
创建对象:var ws = new WebSocket(url,name);
url为WebSocket服务器的地址,name为发起握手的协议名称,为可选择项。
发送文本消息:ws.send(msg);
msg为文本消息,对于其他类型的可以通过二进制形式发送。
接收消息:ws.onmessage = (function(){...})();
错误处理:ws.onerror = (function(){...})();
关闭连接:ws.close();
我们借助这个测试接口 wss://echo.websocket.org
来做一个小demo。
公用html(下面的代码基本也是这个结构):
客户端简单例子
这里我们走Kaazing WebSocket为我们提供的接口,这个接口将完整返回我们所发送的数据。
状态:
返回数据:
JS:
var show = document.getElementById('state'),
msg = document.getElementById('msg'),
st = document.getElementById('sendText'),
sb = document.getElementById('sendBtn');
if ("WebSocket" in window) {
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function(e) {
show.innerText = 'WebSocket连接成功~';
ws.send('Hello WebSockets!');
};
ws.onmessage = function(e) {
msg.innerText = e.data;
};
ws.onclose = function(e) {
show.innerText = 'WebSocket连接关闭~';
}
sb.addEventListener('click',function(){
ws.send(st.value);
})
}else{
alert('你的浏览器不支持WebSocket');
}
nodejs-websocket
nodejs-websocket是一个nodeJs的模块,我们可以用它来轻易地为我们之前的代码单独搭建一个WebSocket的nodeJs服务端。
yarn add nodejs-websocket
var ws = require("nodejs-websocket")
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
console.log("New connection")
conn.on("text", function (str) {
console.log("Received "+str)
conn.sendText(str.toUpperCase()+"!!!")
})
conn.on("close", function (code, reason) {
console.log("Connection closed")
})
}).listen(8001)
Socket.io
在某种程度上,socket.io就是websocket,其实socket.io与websocket不是一回事,而且websocket可以说是socket.io的一个子集,socket.io的底层实现其实有5种方式,websocket只是其中一种,只不过在默认的情况下,我们建立的socket.io连接,底层也是调用websocket的实例。当我们io.connect()建立一个socket连接的时候,返回的是namespace实例,namespace实例中有个socket实例,当新建一个连接,或者发送一条消息的时候,namespace->socket->transport->websocket(xhrpolling...),其实发送一条消息真正的发送者还是底层的websocket或是xhrpolling或其他的几种,而socket.io只是一个组织者,当我们需要建立连接的时候,它自己会在其内部挑选一种连接方式,然后实现连接。
Socket.io都实现了Polling中的那些通信机制呢?
- Adobe® Flash® Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
WebSocket和HTTP和Socket
应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有一些相似的地方,而且有人也会把WebSocket和Socket混为一谈,那么他们之间到底有什么异同呢?
WebSocket和HTTP
我们先看两个协议的截图来领会下。
相同点
- 都是基于TCP的应用层协议。
- 都使用Request/Response模型进行连接的建立。
- 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
- 都可以在网络中传输数据。
不同点
- WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
- WS的连接不能通过中间人来转发,它必须是一个直接连接。
- WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
- WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
- WS的数据帧有序。
WebSocket和Socket
其实就像Java和JavaScript一样,WebSocket和Socket并没有太大的关系。
Socket可以有很多意思,和IT较相关的本意大致是指在端到端的一个连接中,这两个端叫做Socket。对于IT从业者来说,它往往指的是TCP/IP网络环境中的两个连接端,大多数的API提供者(如操作系统,JDK)往往会提供基于这种概念的接口,所以对于开发者来说也往往是在说一种编程概念。同时,操作系统中进程间通信也有Socket的概念,但这个Socket就不是基于网络传输层的协议了。
Socket 其实并不是一个协议。它工作在 OSI 模型会话层(第5层),是为了方便大家直接使用更底层协议(一般是 TCP 或 UDP )而存在的一个抽象层。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层 TCP/IP 协议来建立 TCP 连接。建立 TCP 连接需要底层 IP 协议来寻址网络中的主机。我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例唯一代表一个主机上的一个应用程序的通信链路了。
而 WebSocket 则不同,它是一个完整的 应用层协议,包含一套标准的 API 。
所以,从使用上来说,WebSocket 更易用,而 Socket 更灵活。
浏览器支持
websocket api在浏览器端的广泛实现似乎只是一个时间问题了, 值得注意的是服务器端没有标准的api, 各个实现都有自己的一套api, 并且tcp也没有类似的提案, 所以使用websocket开发服务器端有一定的风险.可能会被锁定在某个平台上或者将来被迫升级。
本文相关的Demo已经放在作者的Github上: 小楼兰的github