图像Ping

这种技术的原理就是利用<img>标签.一个网页可以从任何站点加载图片而无需担心跨域问题,这也是在线广告跟踪浏览量的主要方式.我们也可以动态创建图像,使用它们的onloadonerror事件处理程序来确定是否收到了响应.

动态创建图像经常用于图像Ping.图像Ping是与服务器进行简单的,单向的跨域的一种方式.请求的数据是通过查询字符串的形式发送的,而响应可以是任何内容,但通常是像素图或者204响应.通过图像Ping,浏览器得不到任何具体的数据,但通过侦听loaderror事件能知道响应什么时候接收到.例如:

1
2
3
4
5
var img = new Image()
img.onload = img.onerror = function(){
console.log('done')
}
img.src = 'http://www.test.com/test?name=Mice'

以上的请求在设置src属性的那一刻就开始了,图像Ping最常用于跟踪用户点击页面或者动态广告曝光次数.图像Ping有2个缺点:①只能发送GET请求;②无法访问服务器的响应文本.因此图像Ping只能用于浏览器和服务器之间的单向通信.

JSONP

json with padding(填充式json或者参数式json),jsonp使用一个函数将真实的JSON包围了起来:

1
callback({"name":"Jetty"})

上面的例子展示了JSONP的2个组成部分回调函数和数据,下面是一个典型的JSONP请求:

1
http://freegeoip.net/json/?callback=handleResponse

这个URL是在请求一个JSONP地理定位服务。通过查询字符串来指定JSONP服务的回调参数是很常见的,就像上面的URL所示,这里指定的回调函数的名字叫handleResponse()。JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL。这里的<script>元素与<img>元素类似,都有能力不受限制地从其他域加载资源。因为JSONP是有效的JavaScript代码,所以在请求完成后,即在JSONP响应加载到页面中以后,就会立即执行。来看一个例子。

1
2
3
4
5
6
7
function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " +
response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

JSONP能够得到服务器的响应文本,但是加载了来自外域的js,安全性较低.

Comet

通常叫做服务器推送,实时性非常高.实现Comet有2种方式长轮询和流.

长轮询是传统轮询(也称为短轮询)的一个翻版,段轮询是浏览器定时向服务器发送请求,看有没有更新的数据。如图所示:

短轮询

长轮询把短轮询颠倒了一下。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。如图所示:

长轮询

无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用XHR对象和`setTimeout()``就能实现。而你要做的就是决定什么时候发送请求。

第二种流行的Comet实现是HTTP流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。PHP中实现流的方式常见的如下:

1
2
3
4
5
6
7
8
9
10
<?php
$i = 0;
while(true){
//输出一些数据,然后立即刷新输出缓存
echo "Number is $i";
flush();
//等几秒钟
sleep(10);
$i++;
}

在Firefox、Safari、Opera 和Chrome中,通过侦听readystatechange事件及检测readyState的值是否为3,就可以利用XHR对象实现 HTTP流。在上述这些浏览器中,随着不断从服务器接收数据, readyState的值会周期性地变为3。当readyState值变为3时,responseText属性中就会保存接收到的所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。使用XHR对象实现HTTP流的典型代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function createStreamingClient(url, progress, finished) {
var xhr = new XMLHttpRequest(),
received = 0;

xhr.open("get", url, true);
xhr.onreadystatechange = function() {
var result;

if (xhr.readyState == 3) {
//只取得最新数据并调整计数器
result = xhr.responseText.substring(received);
received += result.length;
//调用 progress 回调函数
progress(result);
} else if (xhr.readyState == 4) {
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("streaming.php", function(data) {
alert("Received: " + data);
}, function(data) {
alert("Done!");
});

服务器发送事件

SSE(Server Sent Event),用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的JavaScript API能解析格式输出。SSE支持短轮询、长轮询和HTTP流,而且能在断开连接时自动确定何时重新连接。

SSE API

要预订新的事件流,首先要创建一个新的EventSource对象,并传进一个入口点.

1
var source = new EventSource("myevents.php"); // 注意同源策略

EventSource的实例有一个readyState属性,值为0表示正连接到服务器,值为1表示打开了连接,值为2表示关闭了连接。另外还提供了3个事件:

  • open :在建立连接时触发。
  • message :在从服务器接收到新事件时触发。
  • error :在无法建立连接时触发。
1
2
3
4
source.onmessage = function(event){
var data = event.data;
//处理数据
};

服务器发回的数据以字符串形式保存在event.data中。默认情况下,EventSource对象会保持与服务器的活动连接。如果连接断开,还会重连。这就意味着SSE适合长轮询和HTTP流。如果想强制立即断开连接并且不再重新连接,可以调用close()方法。

Web Socket

在JavaScript中创建了Web Socket之后,会有一个HTTP请求发送到服务器以发起连接。在取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为WebSocket 协议。需要注意的是:服务端必须支持web socket协议.ws://,wss://(加密).

使用自定义协议而非HTTP协议的好处是,能够在客户端和服务器之间发送非常少量的数据,而不必担心HTTP那样字节级的开销。由于传递的数据包很小,因此Web Sockets非常适合移动应用。

构造websocket必须给其构造函数传递绝对路径(可以是任意的url),不受同源策略影响(因为可通过握手知道信息来自何方).实例化了WebSocket对象后,浏览器就会马上尝试创建连接。和XHR类似,它也有一个readyState属性:

  • WebSocket.OPENING (0):正在建立连接。
  • WebSocket.OPEN (1):已经建立连接。
  • WebSocket.CLOSING (2):正在关闭连接。
  • WebSocket.CLOSE (3):已经关闭连接。

readyState的值永远从0开始。要关闭Web Socket连接,可以在任何时候调用close()方法,调用了close()之后, readyState的值立即变为2(正在关闭),而在关闭连接后就会变成3.

Web Sockets只能通过连接发送纯文本数据,所以对于复杂的数据结构,在通过连接发送之前,必须进行序列化。