从Ajax到WebSocket演变
Ajax全名为异步JavaScript 和XML,它已经变成了使用Javascript 与运程服务异步通信的代名词,它不需要使用XML,在采用了Ajax之后,浏览器中的Web应用程序现在可以与服务器组件通信,而不需要改变浏览器页面或者刷新。
1 频繁轮询
以一个固定的频率,通常每秒依次,浏览器将发送Ajax请求到服务器查询新数据。如果没有,就返回空。
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。
2 长轮询
客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求。
缺点:服务器hold连接会消耗资源。
- 如果浏览器在服务器响应之前有新数据要发送,浏览器必须创建新的并行请求,或者它必须终止当前请求(此时服务器必须能够正确恢复)然后创建新的请求。
- TCP和HTTP规范中都指定了连接超时。因此,服务器和客户端必须周期性第关闭和重新连接。通常连接需要每60秒关闭一次,尽管有些实现可以成功地保持连接几分钟而不关闭。
- HTTP/1.1规范中存在着强制的连接限制。浏览器最多只允许同时创建两个相同主机名的连接。如果一个连接长期连接到服务器等待数据推送,那么它将会减少一半可用于从服务器抓取Web页面、图形和其他资源的连接。
实例:WebQQ、Hi网页版、Facebook IM。
另外,对于长连接和socket连接也有区分:
长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
优点:消息即时到达,不发无用请求。
缺点:服务器维护一个长连接会增加开销。
实例:Gmail聊天
3 分块编码
分块编码非常类似长轮询,它利用了HTTP /1.1的特性:服务器可以在不同声明内容长度的情况下响应请求。响应在不使用Content-Length:n 头时,可以使用Transfer-Encoding:chunked头。这将告诉浏览器响应对象将被“分块发送“,在响应中,每个块的开头依次是:一个用于表示块长度的数字、一系列表示快扩展的可选字符和一个CRLF序列。接着是块包含的数据和另一个CRLF。可以使用任意数目的块,并且它们也可以是不连续的,理论上可以使用任意的时间间隔,或大或小均可。当收到的块长度为0时,表示响应结束。
通常使用块编码解决实际问题的方式是:在开始的时候创建一个连接,只用于接收服务器发送的事件。来自服务器的每个块都是一个新的事件,它们将触发Javascript XMLHttpRequest对象的onreadystatechange事件处理器的调用。偶尔,尽管不如长轮询那么频繁,但连接仍需要刷新。当浏览器需要发送新数据到服务器时,它将使用第2个短生命请求。
4 Applet和Adobe Flash
建立浏览器与服务器之间通过单个连接模拟全双工通信。使用Java Applet和Adobe Flash影片。这种方式消除了所有HTTP协议中存在的限制。当服务器发送消息到浏览器时,Applet或Flash将调用JavaScript函数,并使用消息数据作为参数。当浏览器有了新的数据要发送到服务器,它将使用有浏览器插件暴露出的JavaScript DOM函数调用Java或Flash方法,然后该方法将把数据转发到服务器上。
优点:实现真正的即时通信,而不是伪即时。
缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
实例:网络互动游戏。
5 WebSocket
RCF2616 中的HTTP/1.1 规范有一个特性,http升级:所有HTTP客户端都可以在请求中包含投名称和值Upgrade。为了表示客户端希望升级,在额外的Upgrade头中必须指定一个或多个协议的列表。这些协议必须是兼容HTTP/1.1的协议,例如IRC 或RTA。
WebSocket连接首先将使用非正常的HTTP请求以特定的模式访问一个URL。URL的模式ws和wss分别对应于HTTP的http和https。
WebSocket API
HTML5(JavaScript)客户端API
1) 创建一个WebSocket对象
var connection = new WebSocket('ws://www.example.net/stocks/stream');
var connection = new WebSocket('wss://www.example.net/stocks/stream');
var connection = new WebSocket(‘ws://www.example.net/stocks/stream’,'chat');
var connection = new WebSocket('ws://www.example.net/stocks/stream',{'chat.v1','chat.v2'});
第一个参数为希望连接的WebSocket服务器要求使用的URL。可选的第二个参数为字符串或者字符串数组,它定义了希望使用的一个或多个客户端定义的协议。
2)WebSocket对象
WebSocket接口中主要存在几个属性,第一个属性readyState表示当前WebSocket连接状态,它的值总是CONNECTING(0)、OPEN(1)、CLOSING(2)、CLOSED(3)。
if(connection.readyState == WebSocket.OPEN) {
/* do something*/
}
四种事件,当readyState由CLOSING转为CLOSED时调用onclose事件,当从CONNECTING转换为OPEN时调用onopen事件,
connection.onopen = function(event){}
connection.onclose = function(event){}
connection.onerror = function(event){}
connection.onmessage = function(event){}
传入onopen的是一个标准的JavaScript事件。传入onclose的事件有3个有用的属性:wasClean、code和reason。我们使用这些属性向用户报告一些非正常关闭的信息
connection.onclose = function(event)
{
if(!event.wasClean)
alert(event.code +':' + event.reason);
}
onerror和onmessage的参数中包含data属性。如果消息是一个文本消息,该属性就是字符串,如果是一个二进制消息,它就是一个Blob数据并且WebSocket的binaryType属性将被设置为“blob”(默认),如果消息是一个二进制数据并且binaryType被设置为“arraybuffer”,那么该属性的值将是一个arraybuffer。
var connection = new WebSocket('ws://www.example.com');
connection.binaryType = 'arraybuffer';
WebSocket 对象有两个方法:send和close。
send方法,接收一个字符串、blob、arraybuffer或arraybufferview作为唯一参数;
close,接收一个可选的关闭代码作为它的第一个参数(默认为1000),一个可选字符串reason作为它的第二个参数(默认为空)。
3) WebSocket服务器端
在Java EE web 容器中 都不需要获取ServerContainer。只需要使用@ServerEndpoint标注服务器终端即可
@ServerEndpoint("/game/{gameType}")
如果应用部署到地址为http://www.example.com/app,那么该服务器终端将会响应地址ws://www.example.com/app/game/chess、wss://www.example.com/app/game/checker等。
然后服务器终端中的所有@OnOpen、@OnClose、@OnError 和@OnMessage方法都可以使用@PathParam(“gameType”)获取额外参数。
@ServerEndpoint("/game/{gameType}"
public class GameServer{
@OnOpen
public void onOpen(Session session, @PathParam(gameType) String gametype){
}
}