关于服务端推送技术,大家比较熟悉的可能就是轮询,但是轮询只能是由客户端先发起http请求。在HTTP1.1中的keep-alive方式建立的http连接,但是一个Request只能对应一个Response,而且这个Response是被动的,不能主动发起。
为了在IM、股票等场景下,真正解决服务端实时向客户端推送数据的问题,出现了基于TCP的长连接通讯协议websocket。
websocket协议本身需要注意的一共有两点:
1.为了兼容现有浏览器的握手规范而借用了HTTP的协议来完成一部分握手。
2.websocket是纯事件驱动的,一旦连接建立,通过监听事件可以处理到来的数据和改变的连接状态,数据都以帧序列的形式传输。服务端发送数据后,消息和事件会异步到达。websocket编程遵循一个异步编程模型,只需要对websocket对象增加回调函数就可以监听事件。
下面来具体介绍一下websocket的这两点特征,先介绍基于回调函数的事件编程:
异步编程模型
如果大家想切身体验一下websocket,可以自己下载现成demo。地址:https://github.com/sockjs/sockjs-node/tree/v0.3.19(注意:请通过download下载文件的方式下载代码,不要直接clone,不然代码版本不一样)。不过为了更好地实现浏览器兼容性,demo中是通过sockjs-node库来实现websocket协议的底层协议。更多的sockjs的api请进一步参考git网页中sockjs-node的文档。
本次用来演示的是example文件夹中的koa项目,在代码中大家可以着重理解上面提到的websocket需要注意的第二点,在代码中,主要是通过事件回调函数的方式来实现对消息的处理的。
在服务器代码中首先为sockjs_echo变量赋值了一个websocket连接实例,定义接收到消息后的回调函数,即把接收到的message进行回写。然后启动了一个koa服务器,当被访问的时候返回一个index.html文件,作为webSocket的客户端。最后通过installHandlers方法,在访问路由/echo的时候为koa服务器增加websocket连接的回调处理。
var koa = require('koa');
var sockjs = require('sockjs');
var http = require('http');
var fs = require('fs');
var path = require('path');
// 1. Echo sockjs server
var sockjs_opts = {sockjs_url: "http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js"};
var sockjs_echo = sockjs.createServer(sockjs_opts);
sockjs_echo.on('connection', function(conn) {
conn.on('data', function(message) {
conn.write(message);
});
});
// 2. koa server
var app = new koa();
app.use(function *() {
var filePath = __dirname + '/index.html';
this.type = path.extname(filePath);
this.body = fs.createReadStream(filePath);
});
var server = http.createServer(app.callback());
sockjs_echo.installHandlers(server, {prefix:'/echo'});
server.listen(9999, '0.0.0.0');
console.log(' [*] Listening on 0.0.0.0:9999' );
而在客户端实现如下,通过sockjs库实例化websocket连接实例后,通过onopen、onmessage、onclose这些回调函数来实现消息的发送、接收、连接关闭处理:
var sockjs_url = '/echo';
var sockjs = new SockJS(sockjs_url);
sockjs.onopen = function() {print('[*] open', sockjs.protocol);};
sockjs.onmessage = function(e) {print('[.] message', e.data);};
sockjs.onclose = function() {print('[*] close');};
form.submit(function() {
print('[ ] sending', inp.val());
sockjs.send(inp.val());
inp.val('');
return false;
});
借用HTTP协议完成握手
启动koa服务后,我们访问http://0.0.0.0:9999/打开页面,可以在浏览器network选项卡中观察到连接的建立过程。在request的header中通过"Connection:Upgrade;Upgrade:websocket"字段表示浏览器通知服务器,如果可以,就升级到websocket协议。response中同样通过"Connection:Upgrade;Upgrade:websocket"来通知浏览器,服务端已经成功切换协议,返回状态码为101。
我们此时可以试着在网页中输入一些内容来通过websocket来发送,还是在浏览器的Network选项卡中可以看到我们发送的消息和服务端回写回来的消息:
目前我们已经看到了一个简单的websocket实现,但是在websocket中传输的内容是不是还可以更加丰富一些,比如像http协议一样,添加上各种header字段来让我们实现出一些更加规范的语义协议?可以的。stomp协议就是这样的一个语义协议,可以在websocket的基础之上,通过stomp协议来更加规范地传输消息。
官方文档:http://jmesnil.net/stomp-websocket/doc/
翻译文档:https://blog.csdn.net/quanyuejie/article/details/53896140
使用了stomp协议在websocket基础上传输消息的效果如下,我们可以看到在websocket的消息中,一个消息可以看作一个frame,每个frame中有自己的command和headers、body。比如在连接帧中发送了"CONNECT"的命令,后面accept-version作为headers来定义版本号,heart-beat来定义心跳等等这些功能,都可以看作是通过stomp协议来实现的:
参考: