在当前的web项目中, 涉及服务器反推的应用场景也来越多,许多应用场景也已经不再满足于通过轮询的方式来时实现交互,而是希望能向socket一样,长时间的连接。这里利用最近时间稍微有点富裕,来整理一下关于服务器反推的相关技术,与君共勉,有更好的实现方式或者对于提到的技术优缺点大家可以一起来讨论一下。
废话不多说了,直接开干。
其实目前实现服务器反推总的来说分为两大类:
一,长连接;
二,短连接;
短连接
其实就是通过轮询的方式,在短时间内连续向服务器发送ajax请求,这种方式属于比较low的方式,我们不推荐;这种方式非常的浪费带宽和服务器资源,同时影响浏览器性能;当然万事万物都是有他存在的价值的,如果对于实时性不是很高,比如可以接受几分钟刷新一次的,那么还是可以使用这种方式的。
setInterval(function(){
$.ajax({
//....
})
},50000)
长连接:
长连接其实可以分为两种,一种是伪长连接,另外就是正真意义上的长连接;
伪长连接
伪长连接以comet技术为代表,为什么我要把他归类到伪长连接? Comet 技术是轮询的一种变体。comet技术又分两种,一种是长轮询机制,一种称为流技术,这两种方式实际上是对轮询技术的改进。
长轮询机制(long polling)通常将连接保持一段较长的时间 — 通常是数秒钟,但是也可能是一分钟甚至更长。当服务器上数据一旦可用,便立即从服务器发送到客户机,请求可能等待较长的时间,期间没有任何数据返回,但是一旦有了新的数据,它将立即被发送到客户机同时连接关闭,然后重新发送一次请求。这样反复下去。
流技术(streaming)是服务器将数据推回客户机,但是不关闭连接。连接将一直保持开启,直到过期,并导致重新发出请求。XMLHttpRequest 规范表明,可以检查readyState 的值是否为 3 或 Receiving(而不是 4 或 Loaded),并获取正从服务器 “流出” 的数据。和长轮询一样,这种方式也没有延时。当服务器上的数据就绪时,该数据被发送到客户机。这种方式的另一个优点是可以大大减少发送到服务器的请求,从而避免了与设置服务器连接相关的开销和延时。不幸的是,XMLHttpRequest 在不同的浏览器中有很多不同的实现。这项技术只能在较新版本的Mozilla Firefox 中可靠地使用。对于Internet Explorer 或 Safari,仍需使用长轮询。
实现:
后台使用nodejs,使用socket.io。socket.io对于高本浏览器使用的是websocket,对于不支持webscoket的浏览器使用comet技术(polling);
nodejs:
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(80);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
前端代码:
<script src="/socket.io/socket.io.js">script>
<script>
var socket = io.connect('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
script>
长连接:
目前常见的有websocket和SSE,这两种都是浏览器和服务器之间保持着长连接。
Websocket
Websocket是一个全新的、独立的协议,基于TCP协议,与http协议兼容、却不会融入http协议,仅仅作为html5的一部分。他会在在浏览器和服务器间建立一个全双工的通信通道,即服务器和浏览器之间可以相互通信。
原理
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条通道。两者之间就直接可以数据互相传送。
浏览器请求
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://服务器地址
Sec-WebSocket-Version: 13
服务器回应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
WebSocket借用http请求进行握手,相比正常的http请求,多了一些内容。其中,
Upgrade: websocket
Connection: Upgrade
表示希望将http协议升级到Websocket协议。
Sec-WebSocket-Key是浏览器随机生成的base64 encode的值,用来询问服务器是否是支持WebSocket。
服务器返回
Upgrade: websocket
Connection: Upgrade
告诉浏览器即将升级的是Websocket协议
Sec-WebSocket-Accept是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码得到的。用来说明自己是WebSocket助理服务器。
Sec-WebSocket-Version是WebSocket协议版本号。RFC6455要求使用的版本是13,之前草案的版本均应当被弃用。更多握手规范详见RFC6455。
实现
不同的后台语言对于websocket的API实现是不一样的。
一种是直接使用websocket的api
nodejs:websocket的包有很多种,目前ws是一个比较简洁的。
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({
port: 3000, //监听接口
verifyClient: socketVerify //可选,验证连接函数
});
function socketVerify(info) {
//传入的info参数会包括这个连接的很多信息,你可以在此处使用console.log(info)来查看和选择如何验证连接
return true;
}
// 初始化
wss.on('connection', function(ws) {
// console.log(ws.clients.session);
// console.log("在线人数", wss.clients.length);
ws.send('你是第' + wss.clients.length + '位');
// 发送消息
ws.on('message', function(jsonStr,flags) {
var obj = eval('(' + jsonStr + ')');
// console.log(obj);
this.user = obj;
if (typeof this.user.msg != "undefined") {
wss.broadcast(1,obj);
}
});
// 退出聊天
ws.on('close', function(close) {
try{
wss.broadcast(0,this.user.name);
}catch(e){
console.log('刷新页面了');
}
});
});
前端代码:
// 创建一个Socket实例
var socket = new WebSocket('ws://localhost:3000');
// 打开Socket
socket.onopen = function(event) {
// 发送一个初始化消息
socket.send('I am the client and I\'m listening!');
// 监听消息
socket.onmessage = function(event) {
console.log('Client received a message',event);
};
// 监听Socket的关闭
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};
// 关闭Socket....
//socket.close()
};
这种实现方式需要浏览器支持,对于低版本浏览器。。。你懂得。
socket.io
socket.io实现了对于低版本浏览器的兼容,就是我们前面提到的comet。对于支持websocket的浏览器他会使用websocket进行通信,如果支持的就降级到使用comet.但是socket.io不能跨域通信,直接使用websocket可以跨域通信。
websocket 和socket.io的取舍看应用场景吧。
SSE(Server-Sent Event,服务端推送事件)
是一种允许服务端向客户端推送新数据的HTML5技术。
与WebSocket相比,它也能从服务端向客户端推送数据,但是不能从客户端向服务端推送。SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。
实现
nodejs:
ar http = require("http");
http.createServer(function (req, res) {
var fileName = "." + req.url;
if (fileName === "./stream") {
res.writeHead(200, {"Content-Type":"text/event-stream",
"Cache-Control":"no-cache",
"Connection":"keep-alive"});
res.write("retry: 10000\n");
res.write("event: connecttime\n");
res.write("data: " + (new Date()) + "\n\n");
res.write("data: " + (new Date()) + "\n\n");
interval = setInterval(function() {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
req.connection.addListener("close", function () {
clearInterval(interval);
}, false);
}
}).listen(80, "127.0.0.1");
前端代码:
if (!!window.EventSource) {
// ...
}
然后,部署SSE大概如下。
var source = new EventSource('/stream');
source.onmessage = function(e){
console.log(e.data);
};
// 或者
source.addEventListener('message', function(e){})
通过上面的实现方式可以看出,其实SSE还是居于http协议的,当连接中断恢复后可以自动重连。同时正因为是基于http的所以也可以作鉴权类的操作。
比较一下SSE和websocket:
SSE是基于http协议的单向推送技术,只能由服务器向浏览器推送。
websocket 是全双工双向通信,而且是基于tcp/ip新的应用协议和http应该是同级的,因为其实http也是基于tcp/ip的应用协议。
除了上面的服务器反推技术还有一种,
DWR2.x的推技术
目前还不是很了解,因为已经好久没写java代码了,这方面不是恒了解。座椅暂时不讲这种方式。
还是那句话可能上面有很多“通假字”,请见谅!