反向Ajax

——服务器推送技术介紹

首先,一般web场景都是这样的...

    我想换身衣服,给我些css...

给我些png图片 & font文件,我想多些点缀

    来吧,JavaScript!快来检测的V8引擎的性能...

    要更新点数据,我发了Ajax请求,快点回复...

面对这些请求,服务器都会很「土豪」的说:

只要我有,我都會滿足...

此时,服务器总是被动响应

可是,我们总会遭遇这样的需求...

  • Web实时监控系统
    • 如"树莓派"监控实时交易额
    • 股价实时更新
  • 即时通信服务
    • Web端QQ
  • 新数据推送
    • 网页端的邮件系统

这样的场景有两个特点:

  • 客户端与服务器交互时间间隔短
  • 某些场景,需服务器主动联系客户端

这些需服务器主动通知客户端的需求该怎么解决呢?

基于客户端socket

import flash.errors.*;
import flash.events.*;
import flash.net.Socket;

class CustomSocket extends Socket {
    private var response:String;

    public function CustomSocket(host:String = null, port:uint = 0) {
        super();
        configureListeners();
        if (host && port)  {
            super.connect(host, port);
        }
    }

    private function configureListeners():void {
        addEventListener(Event.CLOSE, closeHandler);
        addEventListener(Event.CONNECT, connectHandler);
        addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        addEventListener(SecurityErrorEvent.SECURITY_ERROR, 
                            securityErrorHandler);
        addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
    }

    ...
}

与脱离浏览器的客户端Socket编程完全相同

FlashSocket、Silverlight & Applet都是浏览器之外的东西

  • 无法直接通过 JavaScript 更新 HTML 页面的内容

  • 浏览器必须安装配置对应的插件

So,这些都不是最好的实现「服务器推送」方式

Ajax 轮询

最简单的两种方式:

  • 浏览器端不断用Ajax请求判断(polling)
  • 浏览器端和服务器端配合实现长轮询(long-polling)

Ajax polling


// 频繁向服务器发Ajax请求,查询是否有更新
setInterval(function(){
    $.ajax({ url: '/ajax', success: function(data){
        // Do stuff with message
    } });
}, 100);

// 服务器接收请求后立马反馈结果
public function postAjax() {
    if (isUpdate($JOSN_DATA)) {
        echo $JOSN_DATA;
    } else {
        echo '{"msg": "no update..."}';
    }
}

http://10.64.12.173:8099/(树莓派,每分钟查询一次)

Ajax Long-Polling


// 向服务器端发送Ajax请求,得到服务器的反馈后立马重新请求
// 递归实现
function load(){
    $.ajax({ url: '/ajax', success: function(){
        // do something with the data
    }, complete: load, timeout: 20000 });
}

// 服务器接收请求后不会立马反馈结果,满足要求后才反馈
public function postAjax() {
    while(isUpdate($JOSN_DATA)) {
        echo $JOSN_DATA;
    } // 不满足`isUpdate($JOSN_DATA)`,死循环等待
}

这样,不必要的Ajax请求少了许多...

但服务器负担依旧很重...

Comet框架

什么是comet框架?

Comet is a web application model in which a long-held HTTP request allows a web server to push data to a browser, without the browser explicitly requesting it.

来自wikipedia的解释:

  • 基于HTTP长链接
  • 不依赖额外的浏览器插件

两个特点:

实现思路

  • 基于HTTP流
    • XMLHttpRequest
    • 隐藏iframe(htmlfile)    
  • 基于长链接Ajax
    • Ajax long polling
    • JSONP polling
// 实现HTTP流的方式
public static function postHTTPStream() {
    while(true) {
        // 更新数据
        $nowData = update(); 
        // 打印数据
        echo $nowData;

        flush();
        sleep(100);
    }
}

采用流实现的服务器端的形式

  • 不会立马给出全部结果
  • 始终保持一个HTTP链接

浏览器端通过Ajax捕捉流数据:

function createStreamingClient(url, progress, finished) {
    var xhr = new XMLHttpRequest(),
        received = 0;

    xhr.open("post", url, true);
    xhr.onreadystatechange = function() {
        var result;
        console.log(xhr.readyState);
        if (xhr.readyState == 3) {
            result = xhr.responseText.substring(received);
            received += result.length;
            progress(result);
        } else if (xhr.readyState == 4) {
            finished(xhr.responseText);
        }
    };
    xhr.send(null);
    return xhr;
}
  1. 监听`onreadystatechange`事件
  2. 检测readyState值是否为3

不同的Comet框架需解决的问题

  • 兼容性

  • 跨域

  • 中断判断

实现思路优缺点分析:

  • 直接通过XMLHttpRequest实现HTTP流,IE不兼容(新firefox等也不支持),不能跨域
  • JSONP polling:能解决跨域问题,只是Ajax long polling的优化,对服务器负担依旧大
  • Ajax long polling:不能解决跨域问题;服务器负担大
  • 隐藏iframe(htmlfile)是个比较好的实现方式   

通过在 HTML 页面里嵌入一个隐蔵iframe,然后将这个隐蔵iframe的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。

iframe 服务器端并不返回直接显示在页面的数据,而是返回对客户端 Javascript 函数的调用,如:

<script type="text/javascript">js_func(“data from server ”)</script>”

服务器端将返回的数据作为客户端 JavaScript 函数的参数传递;客户端浏览器的 Javascript 引擎在收到服务器返回的 JavaScript 调用时就会去执行代码。

使用iframe标签实现HTTP流:

  • 没有跨域困扰
  • 所有浏览器都兼容iframe

但是也存在一些细节问题

  • IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成
  • IE 上方的图标会不停的转动,表示加载正在进行

来自Google的处理方式

使用一个称为"htmlfile"的 ActiveX 解决了在 IE 中的加载显示问题。

相关链接:What else is burried down in the depth’s of Google’s amazing JavaScript?

中断判断:服务器怎么知道浏览器还在请求中?

添加一个最大等待时间判断

服务器在接收到请求后,阻塞而不立刻返回。如果有新的事件,或者达到超时时间,再响应这个请求;避免服务器的无限等待。

典型产品

新的「宠物」:WebSocket

让服务器和浏览器实现双向通信

Title Text

// 插件WebSocket链接
var ws = new WebSocket("ws://url", "websocket");
ws.onopen = function() {
    console.log('open');
};

// 接收服务器端的数据
ws.onmessage = function(evt) {
    console.log(evt.data);
};

// 向服务器发送客户端数据
ws.send('client data');

// 关闭链接
ws.onclose = function() {
    console.log('close');
};

缺陷:

  • 高端浏览器的普及度太低,不是都支持WebSocket
  • 大部分的WEB服务默认不支持WebSocket协议,需要配置

优点:

  • 不依赖插件,也能像客户端socket编程一样方便;
  • 不需要要像Comet一样,牵扯到许多很`Hack`的技巧;

总结

  • 基于客户端Socket编程

  • 基于Comet 框架
    • Ajax 轮询
    • 长链接HTTP流
  • 基于WebSocket

谢谢!

反向Ajax——服務器推送技術介紹

By Ivan Lyons

反向Ajax——服務器推送技術介紹

介紹幾種常用的「服務器推送」技術實現的方式。

  • 2,917