下面介绍在ARP之上的一个非常热门的技术实现:服务器推送技术。
服务器推送技术(Server Push)是最近Web技术中最热门的一个流行术语,它的别名叫Comet(彗星)。它是继AJAX之后又一个倍受追捧的Web技术。服务器推送技术最近的流行与AJAX有着密切的关系。
随着Web技术的流行,越来越多的应用从原有的C/S模式转变为B/S模式,享受着Web技术所带来的各种优势(例如跨平台、免客户端维护、跨越防火墙、扩展性好等)。但是基于浏览器的应用,也有它不足的地方。主要在于界面的友好性和交互性。由于浏览器中的页面每次需要全部刷新才能从服务器端获得最新的数据或向服务器传送数据,这样产生的延迟所带来的视觉感受非常糟糕。因此很多的桌面应用为了获得更友好的界面放弃了Web技术,或者采用浏览器的插件技术(ActiveX、Applet、Flash等)。但是浏览器插件技术本身又有许多问题,例如跨平台问题和插件版本兼容性问题。
随着AJAX技术的兴起,让广大开发人员又一次看到了使用浏览器来替代桌面应用的机会,并且这次机会非常大。AJAX将整个页面的刷新变成页面局部的刷新,并且数据的传送是以异步方式进行,这使得网络延迟带来的视觉差异将会消失。AJAX还利用DHTML和丰富的JavasSript语言来模拟桌面系统的各种事件和响应过程,以及平滑滚动和拖拽的效果。还不止这些,更有一些IT巨头(Google、Sun、Oracle等)提供了非常丰富的AJAX开发工具,使得开发和调试AJAX应用变得简单高效,并且开发的AJAX应用还可以跨越各种浏览器和操作系统。在这种情况下基于AJAX的Web应用迅速涌起,吞噬着原有桌面系统的份额。聊天工具、邮件阅读器、博客编辑器,甚至是Office办公软件和文字处理软件在浏览器中都有着美丽的外观和几乎可以与桌面系统媲美的交互界面。Google更是提出“有了浏览器和Google,就不需要微软”的口号和策略。在AJAX的世界中,除了传统的CAD设计软件和大型游戏软件等因为对系统硬件的苛刻需求,还离不开桌面系统以外,似乎其他所有的应用都可以变成Web应用了。
但是,在浏览器中的AJAX应用中存在一个致命的缺陷无法满足传统桌面系统的需求。那就是“服务器发起的消息传递(Server-Initiated Message Delivery)”。在很多的应用当中,服务器软件需要向客户端主动发送消息或信息。因为服务器掌握着系统的主要资源,能够最先获得系统的状态变化和事件的发生。当这些变化发生的时候,服务器需要主动地向客户端实时地发送消息。例如股票的变化。在传统的桌面系统中,这种需求没有任何问题,因为客户端和服务器之间通常存在着持久的连接,这个连接可以双向传递各种数据。而基于HTTP协议的Web应用却不行。上节中也提到过,在Web世界中,服务器永远是被动地发送数据,前提是客户端必须先发送请求。浏览器其实并不知道服务器的信息什么时候会有改变,为了模拟实时的交流,或者不想错过某些信息,只能通过轮询(Polling)技术不断刷新页面来获得最新的数据(见图18-5)。这种方式不但浪费服务器的资源,最重要的是每次建立(或关闭)新的HTTP连接都有一定的延迟,这种延迟使得频繁信息传递的应用无法忍受。于是就产生了“服务器推送技术”。
图18-5 Web请求的轮询技术
“服务器推送技术”在很久以前就出现过。例如Netscape曾经推出适用于Push技术的专用浏览器和经过修改的HTML语言。但是这仅仅在特定的浏览器中才能使用,其他流行的浏览器(IE等)就不兼容这种技术。
现在的“服务器推送技术”是保持原有的HTTP协议不变,在服务器端改变处理方式,使得服务器能够使用浏览器已经打开的HTTP连接,主动向浏览器发送消息(见图18-6)。这里关键的技术是要保持原有的HTTP连接不断。一旦拥有持久的连接,服务器就可以根据自己的数据更新,随时地向客户端发送最新的信息。
图18-6 服务器数据推送技术
在GlassFish中,Grizzly通过NIO的技术实现了异步请求服务(ARP),并在ARP之上扩展了服务器推送技术的实现,将其也命名为“Comet”。因为使用了NIO,Grizzly才可以在保持HTTP连接的同时,并不会绑定固定的线程,使得GlassFish具有很好的扩展性,可以很好地同时支持大量的Comet请求。下面我们来分析Grizzly中对Comet的实现。
18.2.1 Comet实现的分析
如图18-7所示,Comet的实现是基于ARP之上的,因此整个框架结构仍然符合ARP的模式。读者可以与“新邮件提醒功能”做一个比较,大部分的代码都相类似。最大的不同就是“新邮件提醒功能”是Grizzly的一个扩展,而Comet却已经是Grizzly的一部分,它与其他Grizzly的核心Java包位于同样重要的位置。所有的Comet的实现都在com.sun. enterprise.web.connector.grizzly.comet包中。
因为是ARP的扩展,所以它的入口仍然是AsyncFilter接口的实现。Comet对AsyncFilter接口的实现是CometAsyncFilter类。这个类的注册比“新邮件提醒功能”要简单,只需要在GlassFish的启动配置文件(domain.xml)中加上<property name="cometSupport" value="true"/>就行了,在SelectorThreadConfig类中就会读取到(见例18.12),并且调用SelectorThread中的enableCometSupport方法(见例18.13)将CometAsyncFilter类注册到系统。
图18-7 Comet实现类结构图
【例18.12】在SelectorThreadConfig类中打开Comet功能:
if (System.getProperty(ENABLE_COMET_SUPPORT) != null){
selectorThread.enableCometSupport(
Boolean.valueOf(System.getProperty(ENABLE_COMET_SUPPORT)).booleanValue());
}
【例18.13】SelectorThread中的enableCometSupport方法:
protected void enableCometSupport(boolean enableComet){
if ( enableComet ){
asyncExecution = true;
setBufferResponse(false);
isFileCacheEnabled = false;
isLargeFileCacheEnabled = false;
asyncHandler = new DefaultAsyncHandler();
asyncHandler.addAsyncFilter(new CometAsyncFilter());
SelectorThread.logger()
.log(Level.INFO,"Enabling Grizzly ARP Comet support.");
} else {
asyncExecution = false;
}
}
在CometAsyncFilter类中,最重要的方法就是doFilter,它是Comet与异步请求处理(ARP)框架的接口。
【例18.14】CometAsyncFilter中的doFilter方法:
public boolean doFilter(AsyncExecutor asyncExecutor) {
AsyncProcessorTask apt =
(AsyncProcessorTask) asyncExecutor.getAsyncTask();
CometEngine cometEngine = CometEngine.getEngine();
try{
if (!cometEngine.handle(apt)) {
return true;
}
} catch (IOException ex){
logger.log(Level.SEVERE,"CometAsyncFilter",ex);
}
return false;
}
从例18.14可以看出,在doFilter方法之中,所有的操作都交给CometEngine的handle方法。
CometEngine是Comet应用中最先接触的类。如果一个Servlet或JSP页面要想成为Comet请求,那么在编程的时候需要经过以下几步。
(1) 获得CometEngine的实例对象,并将需要成为Comet请求的路径注册:
CometEngine cometEngine = CometEngine.getEngine();
CometContext cometContext = cometEngine.register(contextPath);
(2) 注册一个CometHandler:
cometContext.addCometHandler(handler);
(3) 最后,如果有消息发送,可以通过下面的方法通知所有注册的通道:
cometContext.notify(handler);
有关CometContext和CometHandler类,在下面的内容会进行稍微详细的描述。
当请求处理交给CometEngine对象以后,CometEngine以及其他几个类(CometContext和CometHandler等)就会对这个请求的生命周期负起全部的责任。Comet请求和其他的请求不一样,它需要长时间地保持HTTP连接,来保证服务器端能够利用这些连接主动发送消息给浏览器客户端。因此CometEngine并没有使用主线程的Selector(在SelectorThread中运行的Selector,而是使用了自己的Selector对象:CometSelector,而让主线程的Selector负责其他类型的请求读取和处理。CometSelector的主要职责是负责已经注册的Comet请求的生命周期:哪些Comet请求的连接被用户关闭或异常关闭,哪些Comet请求根据配置已经超时。在这些情况下,需要系统释放相应的资源,使得系统更加稳定和健壮。
而CometContext的作用则是应用程序和Comet实现之间的桥梁。CometHandler可以利用它来注册,因此CometContext掌握了当前Comet应用中所有注册了的频道。这样当其中有一个频道利用CometContext来发送消息时,CometContext能够将消息主动发送给所有注册的Handler。这些对象的关系,可以通过一个典型的例子的讲解更加清楚的展现出来。
18.2.2 Comet实例讲解——“聊天室”应用
“聊天室”是一个非常典型的Comet应用。通常的“聊天室”至少需要包含两个基本的功能:发送本人的消息和接受显示别人的消息。这里的Comet应用主要是指接受别人的消息。因为别人什么时候发送了消息浏览器是不会知道的,只有聊天服务器本身知道,如果想要将各种消息实时地通知各个客户端,就需要服务器推送技术。
现有的很多“聊天室”大多使用轮询(Polling)技术,来使得浏览器不断自动刷新以获得最新的消息。这种实现方法在并发用户不太多的情况下还能接受。如果并发用户非常多,服务器的负担就会大大地增加。另外每次重新建立连接所带来的延迟也使得用户不能非常及时地获得最新的消息。综合这些因此,对“聊天室”的最佳实现应该使用Comet技术,也就是“服务器推送技术”。
下面来讲解一个使用GlassFish的Comet来实现的“聊天室”。在本书所附的CD中有详细的代码和步骤来部署和运行“聊天室”应用。
在“聊天室”中,只有一个Servlet和几个JSP页面文件。JSP页面非常简单,只是简单的HTML。所有的请求处理都在Servlet中。
【例18.15】Servlet中的init方法:
...
public void init(ServletConfig config) throws ServletException {
super.init(config);
contextPath = config.getServletContext().getContextPath() + "/chat";
CometEngine cometEngine = CometEngine.getEngine(); // [1]
CometContext context = cometEngine.register(contextPath); // [2]
context.setExpirationDelay(20*1000); // [3]
}
...
从例18.15的代码可以看出,Servlet在初始化的时候做了以下三件事情。
(1) 获得了一个CometEngine的实例对象。上文已经解释过,CometEngine对象是Comet应用的入口。任何Comet应用都需要CometEngine对象来注册Comet请求的路径。
(2) 将当前的路径向CometEngine进行注册。显然,当Comet功能打开的时候,GlassFish不会将所有的请求都认为是Comet请求,而是仅仅当请求的路径和将注册的路径相匹配的时候才会进行Comet处理。注册成功的结果是返回一个CometContext对象。上文已经解释过,CometContext是每个用户之间交流的桥梁。
(3) 设置当前Comet应用的超时的阀值。
【例18.16】Servlet的doPost方法中的部分代码(一):
...
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
String action = request.getParameter("action");
CometEngine cometEngine = CometEngine.getEngine();
CometContext cometContext = cometEngine.getCometContext(contextPath);
...
}
从例18.16的代码中可以看出,在处理Comet请求,与其他用户交互的时候,是需要先获得CometContext的。需要指出的是,CometEngine对象是一个单例对象(Singleton),只会存在一个实例,因此任何时候调用getEngine的方法都会获得同一个实例。
【例18.17】Servlet的doPost方法中的部分代码(二):
...
if (action != null) {
if ("login".equals(action)) {
String username = request.getParameter("username");
request.getSession(true).setAttribute("username", username);
if (firstServlet != -1){
cometContext.notify("User " + username
+ " from " + request.getRemoteAddr()
+ " is joining the chat.<br/>",CometEvent.NOTIFY,firstServlet);
}
...
从例18.17的代码可以看出,当用户登录成功后,除了将用户信息保存到session中之外,还会通过cometContext向所有其他用户发出“新用户登录”的信息。
【例18.18】Servlet的doPost方法中的部分代码(三):
...
else if ("post".equals(action)){
String username = (String) request.getSession(true)
.getAttribute("username");
String message = request.getParameter("message");
cometContext.notify("[ " + username + " ] " + message + "<br/>");
response.sendRedirect("post.jsp");
return;
...
例18.18的代码是在处理用户“说话”的情况。如果用户在自己的发送消息框中向其他在线的用户发送了一些消息,Servlet在处理的时候就是通过cometContext来通知所有的在线用户。
【例18.19】Servlet的doPost方法中的部分代码(四):
...
else if ("openchat".equals(action)) {
response.setContentType("text/html");
String username = (String) request.getSession(true)
.getAttribute("username");
response.getWriter().println("<h2>Welcome "+ username + " </h2>");
CometRequestHandler handler = new CometRequestHandler();
handler.clientIP = request.getRemoteAddr();
handler.attach(response.getWriter());
cometContext.addCometHandler(handler);
return;
...
例18.19的代码演示的是“聊天消息显示”的功能,这才是真正Comet的请求,这个请求的连接是一直保持打开着的,等待着服务器主动将最新的信息发送到浏览器。这段代码中最主要的内容就是向CometContext注册了一个CometHandler。注册之后,这个Handler就会等待服务器端的回调,来完成向浏览器输出的功能。
【例18.20】CometRequestHandler类的onEvent方法:
public class CometRequestHandler implements CometHandler<PrintWriter>{
public void onEvent(CometEvent event) throws IOException{
try{
if (firstServlet != -1 && this.hashCode() != firstServlet){
event.getCometContext().notify("User " + clientIP
+ " is getting a new message.<br/>",CometEvent.NOTIFY,
firstServlet);
}
if (event.getType() != CometEvent.READ){
printWriter.println(event.attachment());
printWriter.flush();
}
} catch (Throwable t){
t.printStackTrace();
}
}
...
}
例18.19的代码解释了CometRequestHandler类在收到了系统的函数回调之后,进入到onEvent方法。在onEvent方法的处理中,仅仅是简单地将系统传递过来的消息通过一直保持的HTTP连接向客户传过去。
当“聊天室”应用运行的时候,用户界面如图18-8所示。其中下半部分是发送消息的部分,它的处理代码对应于例18-18。上半部分是对话消息显示的部分,它的处理代码对应于例18.19。
相关推荐
Java向苹果服务器推送消息是iOS应用开发者经常遇到的需求,用于实时通知用户新的信息或系统状态。APNs(Apple Push Notification service)是苹果公司提供的推送服务,允许开发者将消息推送到用户的iOS设备上。本...
【服务器推送技术】 服务器推送技术是一种网络通信模式,它与传统的客户端请求、服务器响应的HTTP协议有所不同。在传统的HTTP协议中,客户端(如浏览器)需要主动向服务器发送请求获取数据,而服务器推送技术则允许...
利用服务器推送技术实现站内短消息(java) 让client与service建立一个长连接,不用client手动request,service会自动response,当有好友在线的时候,会自动把好友的信息加载到select里,点击好友发送短消息时,会在...
DWR的核心特性是它支持AJAX(Asynchronous JavaScript and XML)以及服务器推送技术,极大地提高了Web应用的用户体验。 **服务器推送技术**: 传统的HTTP协议是基于请求-响应模型的,即客户端发起请求,服务器响应...
【极光推送后台Java代码Demo】是针对极光推送服务的一个示例代码,它展示了如何在Java后端环境中集成和使用极光推送服务。极光推送(JPush)是一款广泛应用于移动应用开发中的消息推送服务,它允许开发者向Android、...
Java接入极光推送服务是移动应用开发者常用的一种技术,它允许开发者通过Java后端服务器向Android和iOS设备发送通知消息。极光推送(JPush)是中国知名的推送服务提供商,为开发者提供稳定、高效的推送解决方案。在...
- **客户端SDK**:集成到移动应用或Web应用中,负责接收和处理服务器推送的消息。 - **服务器端**:处理用户注册、订阅、解订阅等操作,存储设备令牌,以及发送推送消息。 - **消息代理**:负责消息的路由和分发...
总之,服务器推送技术是Web技术的重要组成部分,它通过各种手段实现了服务器主动向客户端发送数据的能力,增强了Web应用的交互性和实时性,推动了Web向更接近桌面应用体验的方向发展。随着技术的进步,未来的服务器...
在移动设备端,需要集成极光推送SDK,注册设备别名或tag,这样当服务器推送消息时,设备才能接收到。对于Android,需要在`onCreate()`方法中调用`JPushInterface.init()`初始化,同时处理推送消息的回调。 8. **...
基于MQTT的推送服务端在Java中的实现是一个常见需求,尤其在实时数据传输和设备通信场景中。本教程将深入探讨如何在Java中构建一个MQTT服务器,结合提供的描述和标签,我们将主要关注以下几个知识点: 1. **MQTT...
本篇文章将详细介绍如何在服务器端使用Java来实现iPhone的推送通知功能。 首先,我们需要了解APNs的工作原理。APNs是苹果公司的远程通知服务,当应用程序在后台或未运行时,可以通过APNs将消息推送到用户的设备上。...
在这个压缩包文件中,包含的是极光推送的Java服务器端集成指南以及Android客户端的示例代码。 一、极光推送服务概述 极光推送(JPush)是专门为开发者设计的推送服务,它允许开发者通过云端API向用户的移动设备发送...
随着Ajax技术的普及,开发者希望在浏览器环境中实现更接近桌面应用的实时交互,而服务器推送正是解决这一问题的关键。 在传统的Web访问机制中,客户端(浏览器)通过HTTP请求获取服务器数据,服务器被动响应,不...
5. **客户端接收**:在JavaScript中,使用DWR提供的API注册回调函数,接收到服务器推送的数据后进行处理和展示。 Java推技术不仅限于DWR,还有其他框架如Comet、Atmosphere等也提供了类似的功能。但DWR因其易用性和...
Java程序需要处理并发推送,同时监控APNs服务器的响应,以防止因过多请求导致的连接被关闭。 7. **自定义通知扩展**: - 自iOS 8开始,可以使用“Category”特性为通知添加自定义操作。在Java代码中,这需要在推送...
在Java开发中,集成华为推送服务是为应用提供消息推送功能的重要步骤,这可以提高用户互动性和应用的活跃度。本篇文章将详细讲解如何在服务端实现华为推送服务的集成,以及如何发送推送消息,同时关注数字角标的自动...
3. **构建推送内容**:利用PushContent.java创建推送消息,包括标题、内容、接收者(设备Token或标签)等信息。 4. **发送推送**:调用XinGePushUtil的相关方法,将构建好的推送内容发送到腾讯云服务器。 5. **...