首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

深入了解 Dojo 的服务器推送技术(1)

深入了解 Dojo 的服务器推送技术(1)

服务器推送技术和 Bayeux 协议简介服务器推送技术的基础思想是将浏览器主动查询信息改为服务器主动发送信息。服务器发送一批数据,浏览器显示这些数据,同时保证与服务器的连接。当服务器需要再次发送一批数据时,浏览器显示数据并保持连接。以后,服务器仍然可以发送批量数据,浏览器继续显示数据,依次类推。基于这种思想,这里我们要引出 Bayeux 协议。
Bayeux 是一套基于 Publish / Subscribe 模式,以 JSON 格式在浏览器与服务器之间传输事件的通信协议。该协议规定了浏览器与服务器之问的双向通信机制,克服了传统 Web 通信模式的缺点。
Bayeux 协议主要基于 HTTP 来传输低延迟的、异步的事件消息。这些消息通过频道 (Channels) 来投递,能够实现从服务器端到客户端、从客户端到服务器端或者通过服务器从一个客户端到另一个客户端的传送。Bayeux 协议的主要目的是为使用了 Ajax 和 Comet 技术的 Web 客户端实现高响应的用户交互。Bayeux 协议旨在通过允许执行者更容易的实现互操作性,来降低开发 Comet 应用程序的复杂性。它解决了共同的消息发布和路由问题,并提供了渐进式的改进和扩展机制。
一般情况下,在 HTTP 协议中,Client 要想获得 Server 的消息,必须先自己发送一个 Request,然后 Server 才会给予 Response。而 Bayeux 协议改变了这个情况,它允许 Server 端异步 Push 自己的消息到 Client 端。从而实现了 Client 和 Server 之间的双向操作模式。
服务器推送技术的一个简单实现基于 Bayeux 协议实现服务器推送技术的方式有很多,可以通过 Flex 或者 Java 的 Applet。基于这两种技术,我们可以建立在客户端建立服务套接字接口,“双向操作模式”自然很容易实现,但是这些方式需要除浏览器以外的运行环境的支持。这里我们希望能采用一种纯脚本的方式,这种方式是不可能建立服务套接字接口的,那如何实现基于 Bayeux 协议的服务器推送呢?其实是可以模拟实现的,主要有两种方式:
1. 基于 HTTP 的长轮询来进行消息通信(基于 Ajax 的长轮询(long-polling)方式)。
2. 基于 Iframe 及 htmlfile 的流(streaming)方式。
这里我们采用第一种方式实现,即:客户端先向服务器端发送一个 HTTP Request,服务器端接收到后,阻塞在那边,等服务器有消息的时候,则返回一个 HTTP Response 给客户端,客户端收到后,断开连接,紧接着再发第二个 HTTP Request,以此反复进行,保持这个“长轮询”。期间,如果连接超时,那么会断开重连,以保持连接。
基于以上的思想,我们来看一下一个简单的实现,这个简单实现是基于 PHP 的。示例很简单,即便没用过 PHP 也能够很容易看明白,而且我们会在后面一一作出解释。
这个示例主要实现这样一个功能:
我们在浏览器里面分别打开三个窗口,并访问同一张页面。修改其中一个页面上的内容,另外两个页面上的内容也随即发生变化(注意:这里不用刷新页面)。这就会给我们一种:数据是服务器推送过来的感觉。
图 1. 简单服务器推送示例 -- 内容修改前我们修改其中第一个窗口(左上)的内容(输入“222”,点击“Send”按钮,发送到后台)。此时不仅第一个窗口的内容变化了,其余两个窗口的内容也随即变化。
图 2. 简单服务器推送示例 -- 内容修改接下来我们来看看示例代码吧:
清单 1. 简单服务器推送 -- 前端代码 HTML
1
2
3
4
5
<form action="" method="get"
onsubmit="comet.doRequest($('word').value);$('word').value='';return false;">
<input type="text" name="word" id="word" value="" />
<input type="submit" name="submit" value="Send" />
</form>




这个是我们所看到的输入框和提交按钮,大家可以注意一下它的“onsubmit”方法:当我们输入内容并点击提交时,它会执行“comet.doRequest($('word').value)”方法向后端发起请求(其实在这之前我们就已经建立了与服务端的长轮询并可随时开始服务器推送数据)。接下来我们来看看这个“comet”是什么样子的以及他的 Request 的具体实现:
清单 2. 简单服务器推送 -- 前端代码 JavaScript
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var Comet = Class.create();
Comet.prototype = {
    timestamp: 0,
    url: './backend.php',
    noerror: true,
    initialize: function(){
    },
    connect: function(){
        this.ajax = new Ajax.Request(this.url, {
            method: 'get',
            parameters: {
                'timestamp': this.timestamp
            },
            onSuccess: function(transport){
                var response = transport.responseText.evalJSON();
                this.comet.timestamp = response['timestamp'];
                this.comet.handleResponse(response);
                this.comet.noerror = true;
            },
            onComplete: function(transport){
                if (!this.comet.noerror) setTimeout(function(){
                                         comet.connect()
                                         }, 5000);
                else
                this.comet.connect();
                this.comet.noerror = false;
            }
        });
        this.ajax.comet = this;
    },
    handleResponse: function(response){
        $('content').innerHTML += '<div>' + response['msg'] + '</div>';
    },
    doRequest: function(request){
        new Ajax.Request(this.url, {
            method: 'get',
            parameters: {
                'msg': request
            }
        });
    }
}

var comet = new Comet();
comet.connect();




我们先看最后两段代码,这里是页面初始化时会执行的代码,其实在这里,我们就建立了一服务端的长轮询,我们来看看“connect”方法的实现吧:
“connect”方法这里是发了一个 Ajax 请求,然后分别设定了成功时(onSuccess)的返回处理和请求完成时(onComplete)的处理(注意 onComplete 不论成功失败都会执行)。我们要挂住这里的 onComplete 方法。可以看到,当请求完成时,如果连接有问题,它会过 5 秒重新连接,;如果没有问题,他会立即重新连接。
相信大家看到这里应该会有点眉目了,这里其实没有什么所谓的恒定不断的连接(类似 TCP 方式),它的真正实现是通过不断的 Ajax 请求实现的。
所以,当我们开启 3 个窗口时,其实我们打开了 3 个模拟的不间断的客户端与服务端的连接,所以他们会即时解到服务端的信息,不需要刷新页面。
我们再来看看服务端的实现,看看他是如何推送的:
清单 3. 简单服务器推送 -- 后端代码 PHP
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
27
$filename  = dirname(__FILE__).'/data.txt';

// 将新消息存入文件中
$msg = isset($_GET['msg']) ? $_GET['msg'] : '';
if ($msg != '')
{
file_put_contents($filename,$msg);
die();
}

// 这是一个无限循环,一旦发现文件被修改,便会跳出循环并返回文件修改数据。如果文件一直没有修改,则会一
// 直处于循环检测状态,此时的 Ajax 连接也会一直保留,直到文件被修改为止,这就是所谓的“长轮询”。
$lastmodif    = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$currentmodif = filemtime($filename);
while ($currentmodif <= $lastmodif) // 检测文件是否被修改
{
usleep(10000); // sleep 10ms to unload the CPU
clearstatcache();
$currentmodif = filemtime($filename);
}

// 返回 JSON 数组
$response = array();
$response['msg']       = file_get_contents($filename);
$response['timestamp'] = $currentmodif;
echo json_encode($response);
flush();




我们可以参照上面的注释理解该代码,其实并不需要多少 PHP 的知识。服务端推送技术不是一个开发用的控件库,而是一个思想。这里的 while 循环便说明了服务端推送是如何保留所谓的“长轮询”的。
现在大家应该明白为什么三个窗口会同步变化了。其主要的核心思想就是服务端“握住”长轮询,然后在适当的时候“放手”。
返回列表