返回首页
当前位置: 主页 > 其他教程 > 电脑教程 >

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

时间:2013-03-27 00:48来源:Office教程学习网 www.office68.com编辑:麦田守望者

简介: 服务器推送技术已经出来一段时间了,业界上也有不少基于这种技术(应该说是设计模式)的开源实现,但是要移植 或者说应用到自己的项目上都比较麻烦。Dojo 这样一个大型的 Web2.0 开发框架提供了一套封装好的基于服务端推送技术的具体实现(包括服务端 Java 和客户端 Web 和 JavaScript),它基于 Bayeux 协议,提供了一些简单而且强大的接口可以让你快速构建自己的服务端推送功能。客户端实现即 Dojo 的 Cometd 前端组件,它封装了建立连接、消息订阅等等接口。服务端基于 Jetty 和 annotation,组建消息推送机制,同样也封装了比较简单但实用的消息推送接口,与前端 Dojox 的 Cometd 接口协同工作。这篇文章将重点介绍 Dojo 的服务端推送机制是如何运作的,以及我们应该如何基于 Dojo 的 Cometd 工具包构建自己的服务端推送功能。

 

服务器推送技术和 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. 简单服务器推送示例 -- 内容修改前
图 1. 简单服务器推送示例 -- 内容修改前
 

我们修改其中第一个窗口(左上)的内容(输入“222”,点击“Send”按钮,发送到后台)。此时不仅第一个窗口的内容变化了,其余两个窗口的内容也随即变化。


图 2. 简单服务器推送示例 -- 内容修改
图 2. 简单服务器推送示例 -- 内容修改
 

接下来我们来看看示例代码吧:


清单 1. 简单服务器推送 -- 前端代码 HTML

				 
 <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

				 
 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

				 
 $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 循环便说明了服务端推送是如何保留所谓的“长轮询”的。

现在大家应该明白为什么三个窗口会同步变化了。其主要的核心思想就是服务端“握住”长轮询,然后在适当的时候“放手”。

Dojo 的 Cometd 工具包简介

之前我们是基于 JavaScript 自己实现了一个简单的 Cometd 应用,我们花了大量的代码来建立一个 Cometd 框架,真正用于处理我们自己的业务逻辑的代码其实就是“handleResponse”里面的那一行。我们能不能吧这些通用的代码省掉呢?答案是肯定的。Dojo 已经对 Cometd 做了封装,基于 Dojo 的 Cometd 包,我们不用再浪费大量的代码在搭建 Cometd 框架上。对于前端脚本代码,我们只需要加上一个 Cometd 包的简单接口代码,便可以开始加入我们自己的业务逻辑代码了。

当然,Dojo 的 Cometd 包还包括后端的代码,可以在 Dojo 的官网下载中找到,它不与 Dojo 包一起发布,是一个单独的服务端开源代码,基于 Java 和 Jetty 的,有兴趣的读者可以下载下来研究一下。

通过 Dojo 的这两部分代码,我们便可以迅速地搭建我们的 Cometd 框架,我们剩下需要做的就是加入我们的业务逻辑。

Dojo 的 Cometd 工具包之前端

接下来我们来看看 Dojo 的 Cometd 工具包的前端封装:


清单 4. Cometd 前端初始化

				 
 dojox.cometd.init("http://www.xxx.com/cometd"); 

 

这个接口用于建立并初始化与服务端的握手连接(Bayeux handshake,初始化了“Bayeux communication” 消息通讯)。建立这个连接是基于 Bayeux 协议的,它主要有两个任务:

  1. 客户端与服务端协商传输的消息类型。
  2. 如果协商成功,服务端会通知客户端具体的请求参数配置。
  3. 如果协商失败,客户端重新发起协商流程。

我们深入 Dojo 的 init 方法内部可以看到握手连接的具体实现过程,它的实现也是不间断的重复发送客户端的 Ajax 请求,与我们之前的自制案类似,有兴趣的同学可以参考如下代码(摘取部分):


清单 5. Cometd 内部机制

				 
 this.init = function(...){
............
	var bindArgs = {
		url: this.url,
		handleAs: this.handleAs,
		content: { "message": dojo.toJson([props]) },
		load: dojo.hitch(this,function(msg){
			this._backon();
			this._finishInit(msg);
		}),
		error: dojo.hitch(this,function(e){
			this._backoff();
			this._finishInit(e);
		}),
		timeout: this.expectedNetworkDelay
	};
..............
	if(this._isXD){
		r = dojo.io.script.get(bindArgs);
	}else{
		r = dojo.xhrPost(bindArgs);
	}
..............
}

this._finishInit = function(data){
..................
	if(successful){
		........
		//ajax request inside
		this.tunnelInit = transport.tunnelInit && dojo.hitch(transport,
        "tunnelInit");
		this.tunnelCollapse = transport.tunnelCollapse && dojo.hitch(transport,
		"tunnelCollapse");
		transport.startup(data);
	}else{
		if(!this._advice || this._advice["reconnect"] != "none"){
			setTimeout(dojo.hitch(this, "init", this.url, this._props),
			this._interval());
		}
	}
....................
}

 

可见,它们的 callback 方法里面都带有对自己本身的调用,这里的”init“方法也不例外。细心的读者可能还会发现,其实从例子上可以看出:Dojo 的 Cometd 也支持跨域,它的跨域是通过“script”的方式实现的。这里有一点需要大家了解,我们默认的服务端推送实现方式是长轮询(long-polling) 模式,遇到跨域时,“long-polling”便不再适用,转为基于“script”的返回调用(callback-polling)模式。

接下来我们再来看看 Cometd 中关于消息推送的一些接口,这些消息通讯主要是基于渠道:

------分隔线----------------------------
标签(Tag):电脑知识 电脑技巧
------分隔线----------------------------
推荐内容
猜你感兴趣