`
chenx
  • 浏览: 6231 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

在Glassfish v2ur1 中测试grizzly comet chat demo

阅读更多
2006年Web 2.0最火的技术莫过于AJAX了,各种AJAX的框架层出不穷。正是应验一句老话:“三十年河东,三十年河西”,其实还不到三十年呢,B/S开发模式已经落伍了,现在C/S模式又吃香了,只不过client不是由OS承载,而是换成了web浏览器;client端的编程语言换成了JavaScript。AJAX似乎可以包办一切,唯一的缺陷是所有业务必须由client端来触发,Server端只能被动地响应。针对AJAX的先天缺陷,Comet技术应运而生,或者说是老树开新花,Server端的推送技术,使得Web浏览器真正成为理想的综合业务通用客户端平台。

Grizzly应用Java NIO框架,利用有限的线程为大量并发的socket连接提供服务,使得Server端有非常出色的扩展性。Grizzly Cometd为我们提供了针对Http请求的异步处理机制,加上对Bayeux协议的支持,我们可以在Cometd Servlet中创建消息队列,浏览器端可以灵活地订阅(Subscribe)消息,当Server端有新的消息需要分发时,将消息推送给订阅者;当然浏览器也可以主动地发布(Publish)消息,Server收到消息后,立即推送给此消息的订阅者(可以是Web浏览器,也可以是特殊的客户端或服务端的进程)。Glizzly Cometd的消息分发采用Bayeux协议。当前能够支持Grizzly+Bayeux的Servlet Container主要是Jetty6Glassfish v2,Tomcat还不支持。在浏览器端,只有Dojo Cometd支持Bayeux协议。

在Jetty下测试Cometd Chat Demo非常顺利,我下载了最新的Jetty 6.1.7,在WinXP(sp2)的DOS窗口中启动Server:
java -jar start.jar etc/jetty.xml etc/jetty-grizzly.xml
使用IE/FF访问 http://localhost:8888/test/chat。

Grizzly是Glassfish的子项目,Glassfish直接使用grizzly的源码,重新打包。我下载的版本是最新发布的V2UR1。Chat Demo(grizzly-cometd-chat.war)来自Jean-Francois Arcand的blog。下载并安装Glassfish V2UR1,配置domains/domain1/config/domain.xml,将ws/tcp注释掉,添加cometSupport支持:
<http-listener id="http-listener-1" address="0.0.0.0" port="8080" acceptor-threads="1" security-enabled="false" default-virtual-server="server" server-name="" xpowered-by="true" enabled="true" family="inet" blocking-enabled="false">
    <!-- property name="proxiedProtocols" value="ws/tcp"/ -->
    <property name="cometSupport" value="true"/>
</http-listener>

将war文件部署到domains/domain1/autodeploy目录,能够看到Cometd Chat登录界面,输入用户名点击"Join"按钮,显示聊天页面(参见cometd1.PNG),但是未显示已登入聊天室的提示信息。检查glassfish的log文件,发现有异常抛出:
引用
StandardWrapperValve[Grizzly Cometd Servlet]: PWC1406: Servlet.service() for servlet Grizzly Cometd Servlet threw exception
java.lang.ClassCastException: java.lang.Double
at com.sun.grizzly.cometd.bayeux.VerbUtils.newHandshake(VerbUtils.java:126)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parseMap(VerbUtils.java:98)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parse(VerbUtils.java:71)
at com.sun.grizzly.cometd.EventRouterImpl.route(EventRouterImpl.java:90)
at com.demo.servlet.ChatDemoServlet.doPost(ChatDemoServlet.java:152)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:738)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:831)
at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:411)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:290)
at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:271)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:202)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:206)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:150)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:272)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:637)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.executeServlet(CometEngine.java:547)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.handle(CometEngine.java:299)
at com.sun.enterprise.web.connector.grizzly.comet.CometAsyncFilter.doFilter(CometAsyncFilter.java:87)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:175)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:153)
at com.sun.enterprise.web.connector.grizzly.async.AsyncProcessorTask.doTask(AsyncProcessorTask.java:92)
at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265)
at com.sun.enterprise.web.connector.grizzly.WorkerThreadImpl.run(WorkerThreadImpl.java:116)

注意:如果使用Glassfish的版本不是V2UR1,可能会得到java.lang.NullPointerException异常。

初步分析错误出自grizzly cometd包对bayeux协议解析时发生数据转型错误。上网抓grizzly的sourcecode,先分析CometdServlet,在分析EventRouter和VerbUtils,发现Glassfish没有使用grizzly的最新版本,再上网抓glassfish的sourcecode。为了便于单步跟踪,在MyEclipse中创建一个Web工程,建立一个ChatDemoServlet替代CometdServlet,建立一个VerbUtils替代原来的VerbUtils,在ChatDemoServlet的doPost()方法中嵌入以下代码:

@Override
public void doPost(HttpServletRequest hreq, HttpServletResponse hres)
        throws ServletException, IOException {
        ......
        String[] messages = hreq.getParameterValues("message");   
        if (messages != null && messages.length > 0){
            for(String message: messages){
            	Object verbObj = JSONParser.parse(message);
                //Verb verb = VerbUtils.parse(verbObj);
                VerbBase wellFormedVerb = null;
                if (verbObj.getClass().isArray()){
                    int length = Array.getLength(verbObj);
                    for (int i=0; i < length; i++){
                        wellFormedVerb = 
                                VerbUtils.parseMap((Map)Array.get(verbObj,i), wellFormedVerb);
                     }
                }
                
                // Notify our listener;
            }
        } 
        ......
}


在MyEclipse中经过单步调试,发现Bug原因是:
JSONParser.parse()对请求参数中的数值进行转型,有小数点时转型为Double,否则转型为Long。而VerbUtils的newSubscribe()只能接收类型为String的参数。改进方案很简单,将VerbUtils的newSubscribe()方法中的以下代码
        handshake.setVersion((String)map.get("version"));
        handshake.setMinimumVersion((String)map.get("minimumVersion"));

替换为
        Object ov = map.get("version");
        if (ov instanceof String)
        	handshake.setVersion((String)ov);
        else if (ov instanceof Number)
        	handshake.setVersion(ov.toString());
        else handshake.setVersion("1.0");
        ov = map.get("minimumVersion");
        if (ov instanceof String)
        	handshake.setMinimumVersion((String)ov);
        else if (ov instanceof Number)
        	handshake.setMinimumVersion(ov.toString());
        else handshake.setMinimumVersion("0.1");


重新编译(技巧:在MyEclipse工程中建立一个com.sun.grizzly.cometd.bayeux包,将glassfish源文件的压缩包展开,将appserv-http-engine/src/main/java/com/sun/grizzly/cometd/bayeux目录下所有的java文件导入到此包中),得到VerbUtils.class文件替换glassfish安装路径的lib目录下的appserv-rt.jar文件中的同名文件。


重新测试,server端log文件中没有异常记录,但是登录和发消息都没有看到预期的消息显示在对话内。采用MyEclipse Web2.0浏览器进行跟踪,发现chat.js工作不正常,没有发出http请求到服务端。由于本人对AJAX/Dojo的功力不足,未进一步分析是何原因。与Jetty6的chat demo对比,发现两者使用dojo版本不同,所以想到使用移花接木的手段,尝试用Jetty6的客户端访问Glassfish的服务端:
1)在MyEclipse工程中WebRoot目录下创建chat目录,导入index.html、chat.js和chat.css(参见附加文件)。
2)下载最新的Dojo API包:dojo-release-1.0.2.zip。解压缩后部署到WebRoot目录下。
3)修改index.html,内容如下:
<html>
<head>
    <title>Cometd chat</title>
    <script type="text/javascript" src="../dojo/dojo.js"></script>
    <script type="text/javascript" src="../dojox/cometd.js"></script>
    <script type="text/javascript" src="chat.js"></script>
    <link rel="stylesheet" type="text/css" href="chat.css">
</head>
<body>
<h1>Cometd Chat</h1>

<div id="chatroom">
 <div id="chat"></div>
 <div id="input">
   <div id="join" >
     Username: <input id="username" type="text"/><input id="joinB" class="button" type="submit" name="join" value="Join"/>
   </div>
   <div id="joined" class="hidden">
     Chat: <input id="phrase" type="text"></input>
     <input id="sendB" class="button" type="submit" name="join" value="Send"/>
     <input id="leaveB" class="button" type="submit" name="join" value="Leave"/>
   </div>
  </div>
 </div>
</body>

4)改编chat.js,内容如下:
dojo.require("dojox.cometd");
//dojo.require("dojox.cometd.timesync");

var room = {
    _last: "",
    _username: null,
    _connected: true,

    join: function(name){
        
        if(name == null || name.length==0 ){
            alert('Please enter a username!');
        }else{
		
            dojox.cometd.init(new String(document.location).replace(/http:\/\/[^\/]*/,'').replace(/\/chat\/.*$/,'')+"/cometd");
            // dojox.cometd.init("http://127.0.0.2:8080/cometd");
            this._connected=true;
	    
            this._username=name;
            dojo.byId('join').className='hidden';
            dojo.byId('joined').className='';
            dojo.byId('phrase').focus();
	    
            // subscribe and join
	    	dojox.cometd.startBatch();
            dojox.cometd.subscribe("/chat/demo", room, "_chat");
            dojox.cometd.publish("/chat/demo", { user: room._username, join: true, chat : room._username+" has joined"});
	    	dojox.cometd.endBatch();
	    
            // handle cometd failures while in the room
            room._meta=dojo.subscribe("/cometd/meta",dojo.hitch(this,function(event){
                console.debug(event);   
                if (event.action=="handshake") {
	            room._chat({data:{join:true,user:"SERVER",chat:"reinitialized"}});
                    dojox.cometd.subscribe("/chat/demo", room, "_chat");
                } else if (event.action=="connect") {
		    if (event.successful && !this._connected)
                        room._chat({data:{leave:true,user:"SERVER",chat:"reconnected!"}});
		    if (!event.successful && this._connected)
                        room._chat({data:{leave:true,user:"SERVER",chat:"disconnected!"}});
		    this._connected=event.successful;
	        }
            }));
        }
    },

    leave: function(){
        if (room._username==null)
            return;
	    
	if (room._meta)
            dojo.unsubscribe(room._meta);
	room._meta=null;
	
		dojox.cometd.startBatch();
        dojox.cometd.unsubscribe("/chat/demo", room, "_chat");
        dojox.cometd.publish("/chat/demo", { user: room._username, leave: true, chat : room._username+" has left"});
		dojox.cometd.endBatch();

        // switch the input form
        dojo.byId('join').className='';
        dojo.byId('joined').className='hidden';
        dojo.byId('username').focus();
        room._username=null;
        dojox.cometd.disconnect();
    },
      
    chat: function(text){
        if(!text || !text.length){ return false; }
        dojox.cometd.publish("/chat/demo", { user: room._username, chat: text});
    },

    _chat: function(message){
        var chat=dojo.byId('chat');
        if(!message.data){
            alert("bad message format "+message);
            return;
        }
        var from=message.data.user;
        var special=message.data.join || message.data.leave;
        var text=message.data.chat;
        if(!text){ return; }

        if( !special && from == room._last ){
            from="...";
        }else{
            room._last=from;
            from+=":";
        }

        if(special){
            chat.innerHTML += "<span class=\"alert\"><span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span></span><br/>";
            room._last="";
        }else{
            chat.innerHTML += "<span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span><br/>";
        } 
        chat.scrollTop = chat.scrollHeight - chat.clientHeight;    
    },
  
  _init: function(){
        dojo.byId('join').className='';
        dojo.byId('joined').className='hidden';
        dojo.byId('username').focus();
	
        var element=dojo.byId('username');
        element.setAttribute("autocomplete","OFF"); 
        dojo.connect(element, "onkeyup", function(e){   
            if(e.keyCode == dojo.keys.ENTER){
                room.join(dojo.byId('username').value);
                return false;
            }
            return true;
	});
  
        element=dojo.byId('joinB');
        element.onclick = function(){
            room.join(dojo.byId('username').value);
            return false;
	}
  
        element=dojo.byId('phrase');
        element.setAttribute("autocomplete","OFF");
        dojo.connect(element, "onkeyup", function(e){   
            if(e.keyCode == dojo.keys.ENTER){
                room.chat(dojo.byId('phrase').value);
                dojo.byId('phrase').value='';
                return false;
            }
            return true;
	});
  
        element=dojo.byId('sendB');
        element.onclick = function(){
          room.chat(dojo.byId('phrase').value);
          dojo.byId('phrase').value='';
	}
  
        element=dojo.byId('leaveB');
        element.onclick = function(){
          room.leave();
	}
    } 
};

dojo.addOnLoad(room, "_init");
dojo.addOnUnload(room,"leave");

5)将此工程打包为cometd.war(Context Root Path必须是/cometd)部署到glassfish的domains/domain1/autodeploy/目录下。

重新测试http://localhost:8080/cometd/chat/index.html,期待已久的Cometd聊天室可以正常工作啦!

  • 描述: cometd chat Join
  • 大小: 6 KB
分享到:
评论
1 楼 liingf117 2008-06-03  
不能用,输入后没有反应

相关推荐

    Glassfish V2 And NetBean6

    Glassfish V2 和 NetBeans 6 是两个在Java EE开发领域中重要的开源工具。这篇博客主要探讨了如何结合这两个工具进行高效的应用程序开发。 Glassfish V2 是Oracle公司(当时称为Sun Microsystems)推出的Java EE应用...

    Glassfish V2与Apache的整合

    《Glassfish V2与Apache整合详解》 在IT行业中,服务器的高效运行和灵活配置是提升应用程序性能的关键。本文将详细介绍如何将Java EE应用服务器Glassfish V2与Web服务器Apache进行整合,以实现更强大的服务架构。...

    glassfish v2 windows all 启动服务

    通过windows sc dos 命令给windows 服务添加启动服务。 sc create SS9PE binPath= "D:\appserver\glassfish-v2ur1\lib\appservService.exe

    glassfish v2 在windows 构建启动服务

    glassfish 安装构建在windows 中自动启动服务。

    glassfish-v2 source code

    在本文中,我们将深入探讨Glassfish-v2的核心概念、架构设计以及关键组件,旨在帮助读者掌握这款应用服务器的内在运作机制。 一、Java EE 5标准概述 Java EE(Java Platform, Enterprise Edition)是Sun ...

    glassfish_demo

    此外,压缩包中的"demo.exe"和"demo.txt"文件可能与演示应用有关,"demo.exe"可能是Windows平台上的一个可执行程序,用于辅助演示或测试,而"demo.txt"可能是包含说明、日志信息或示例数据的文本文件。为了完整体验...

    glassfish安装手册及源文件

    3. **安装Glassfish**:运行下载的jar包,如在Windows上,使用命令`java -Xmx256m -jar glassfish-installer-v2ur2-b04-windows.jar`。在安装过程中,同意许可协议,完成后,你会在当前目录看到新建的Glassfish...

    在Glassfish上部署web应用

    在本文中,我们将深入探讨如何在Glassfish服务器上部署Web应用程序。Glassfish是一款开源的应用服务器,主要用于运行Java EE(现在称为Jakarta EE)应用程序,包括Web应用程序和企业级Java组件。 首先,对于Java EE...

    基于glassfish的EJBDemo,包含打包脚本,包含客户端

    在本EJBDemo中,我们重点关注如何在Glassfish服务器上开发和部署EJB应用。Glassfish是一款开源的Java EE应用服务器,它提供了全面的支持,包括对EJB的运行环境。 1. **EJB基础** EJB分为三种类型:会话Bean...

    glassfish ant eclipse 配置 指南 图解

    下载适用于Windows平台的GlassFish安装文件,文件名为`glassfish-installer-v2ur1-b09d-windows-ml.jar`,大小约为81MB。将下载的文件放置在指定目录,例如`D:\`下,并通过命令行进行安装: ```bash D:\&gt;java –Xmx...

    glassfish和ant安装与配置

    - 在命令行中执行`D:\glassfish\bin&gt;asadmin start-domain domain1`启动GlassFish服务。 - 可以通过浏览器访问http://localhost:4848来登录GlassFish的管理控制台,用户名和密码均为“admin”。 3. **停止...

    JMX 在GlassFish中的应用

    ### JMX 在GlassFish中的应用 #### JMX与GlassFish:深入理解管理与监控 **JMX(Java Management Extensions)**是一种由Sun Microsystems提出并由Java社区推动的标准,旨在为Java应用程序、系统和网络提供一个...

    NetBeans IDE and GlassFish developing demo

    1. **创建项目**:在NetBeans IDE中,通过“新建项目”向导,选择Java EE Web项目,为Web服务创建项目结构。 2. **编写服务接口**:定义SOAP或RESTful服务接口,如使用JAX-WS(Java API for XML Web Services)或...

Global site tag (gtag.js) - Google Analytics