当前位置: 首页 > 工具软件 > Comet4J > 使用案例 >

Spring boot + Maven环境搭建comet4j前端页面推送

洪光霁
2023-12-01

**

问题

**
前段时间项目升级micro server,要求在Spring boot环境下实现主动向前端页面推送信息功能。因为要兼容IE,在websocket和comet之间选择了comet。在谷歌和度娘上没找到现成的方案。于是对照Spring环境下comet4j的demo各步骤,猛啃Spring boot文档,将comet配置的每一个步骤移植到Spring boot框架中。Best Luck,辛苦一周后,终于成功将一段图片Base64信息推送至前端页面。

[参考文献]
http://www.jianshu.com/p/b90a36631469
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle

**

准备工作

**

1.环境

Spring boot + maven + tomcat7
comet4j只能在tomcat6 或 7 下运行。tomcat8下安装没有成功。

2.下载文件。

comet4j-tomcat7.jar
comet4j.js
comet大老板在墙外。不巧珍藏多年的vpn挂了。网上下载来源众多,但不一定都能用。有积分的童鞋可以从CSDN上下载全套。但我最终在github上挖到了我想要的东西。地址后贴。

**

spring boot配置

**

1.Maven配置

远程仓库没有该jar包,需要从本地资源中引用。

<dependency> 
            <groupId>comet4j-tomcat7</groupId> 
            <artifactId>test</artifactId> 
            <version>1.0</version> 
            <scope>system</scope> 
            <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/comet4j-tomcat7.jar</systemPath>
</dependency>

或者将jar包传至私服仓库。

因为项目原先使用的是tomcat8,需要在pom.xml中修改tomcat版本:

    <properties>
        <tomcat.version>7.0.82</tomcat.version>
    </properties>

2.配置tomcat服务器

将spring boot内置tomcat的连接方式改为NIO.
spring boot的web配置使用@Configuration注解的Config类实现,配置类中每一个@Bean注解的方法代表一个以方法名为id,方法返回值为对象的Spring Bean。

新建CometConfig类:

@Configuration
public class CometConfig{
    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
        tomcat.setProtocol("org.apache.coyote.http11.Http11NioProtocol");
        return tomcat;
    }
}

以上代码相当于在传统Web项目的tomcat服务器中作以下配置

<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

NIO的概念请转此处 http://blog.csdn.net/yaerfeng/article/details/7679740

3.监听器与Servlet配置

Comet需要4个监听器和1个Servlet,这些组件原先是配置在web.xml文件中。
Spring boot取消了web.xml配置文件,原先的Listener,Filter和Servlet可以通过两种方法配置。
一种是在Listener和Servlet类名前加注解:@WebListener @WebFilter @WebServlet 。
并在Springboot启动类添加类注解@ServletComponentScan,启用系统组件扫描。
另一种方法仍旧通过Config类@Bean注解配置:

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new XXXServlet(), "/some/urlmapping"); 
        //此处传入<servlet>标签两个参数:Servlet对象, url-mapping
        return servletRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean<EventListener> getCometAppListener(){
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<>();
    //此处传入Listener对象,系统将在初始化时启动监听
        return registrationBean;
    }

配置Listener


comet的运行涉及4个监听器,除了CometAppListener由comet提供外,我们需要编写其余3个Listener类,分别监听comet启动,comet连接,连接断开。其中前二者(CometAppListener,Comet4JListener)属于系统事件监听,必须在Config类中配置。

@Configuration
public class CometConfig{

    @Bean
    public ServletListenerRegistrationBean<EventListener> getCometAppListener(){
        ServletListenerRegistrationBean<EventListener> registrationBean =new ServletListenerRegistrationBean<>();
        registrationBean.setListener(new CometAppListener());
        registrationBean.setOrder(1); //加载顺序
        return registrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean<EventListener> getCometListener(){
        ServletListenerRegistrationBean<EventListener> registrationBean =new ServletListenerRegistrationBean<>();
        registrationBean.setListener(new Comet4JListener());
        registrationBean.setOrder(3);
        return registrationBean;
    }   

    //...更多配置
}

其余两个类由Comet管理,不必向系统注册。我们在Comet4JListener的contextInitialized(ServletContextEvent sce) 方法中看到以下代码:

        CometContext cc = CometContext.getInstance();
        CometEngine engine = cc.getEngine();
        engine.addConnectListener(new JoinListener()); 
        // 注册发起连接监听
        engine.addDropListener(new LeftListener()); 
        // 注册离开监听

这就将JoinListener类和LeftListener类注册到Comet的事件监听中。

Comet4JListener和JoinListener和LeftListener的编写与Spring环境下代码并无本质区别。请参考原文:
http://www.jianshu.com/p/b90a36631469
这是另一种写法:
http://blog.csdn.net/ntotl/article/details/51803641


配置Servlet


Comet中需要的CometServlet类由Comet系统提供。我们只要配置相应的url-mapping就可以,也就是前端js发起连接时的请求地址。

@Configuration
public class CometConfig{
    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new CometServlet(), "/demo/cometconn");
         //客户端代码发起连接的请求地址
        servletRegistrationBean.setAsyncSupported(true);
        servletRegistrationBean.setOrder(2);//加载顺序
        return servletRegistrationBean;
    }
    //...更多配置
}

这里要注意到,如果新建ServletRegistrationBean类时编译不通过,报错缺少CometProcessor类,很可能是tomcat版本不兼容。因为只有tomcat6 或 tomcat7提供了相应的支持类。


自此spring boot comet的基本配置已完成,接下去写业务逻辑。


**

代码编写

**

服务端代码

服务端需要编写4个类。因为代码逻辑大同小异,这里只摘取关键代码段。
1.
Comet4JListener implements ServletContextListener
系统启动时作一些初始化工作。规定向前端推送的频道并注册comet连接事件监听。

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        CometContext cc = CometContext.getInstance();
        CometEngine engine = cc.getEngine();

        cc.registChannel("hello_channel_1");
        cc.registChannel("kick_channel_2");
        //可注册多个channel,作为服务端向前端推送内容的通道。

        engine.addConnectListener(new JoinListener());
        engine.addDropListener(new LeftListener());
        //注册comet连接与离开事件监听
    }

2.
JoinListener extends ConnectListener
监听客户端发起连接事件。
每次连接传入一个唯一的cometConnectionId,可以通过这个id决定相应的内容是否推送到特定的前端页面。相应代码如下:

    @Override
    public boolean handleEvent(DropEvent drEvent) {
        //...
        CometConnection conn = drEvent.getConn();
        String connId = conn.getId();
        //...
    }

3.
LeftListener extends DropListener
监听前端连接断开(如关闭页面时)。同样可以获取一个cometConnectionId:

    @Override
    public boolean handleEvent(DropEvent drEvent) {
        //...
        CometConnection conn = drEvent.getConn();
        String connId = conn.getId();
        //...
    }

4.
CometFaceSendUtil
向前端推送消息工具类。
推送到指定前端页面:

    CometEngine engine = CometContext.getInstance().getEngine();
    engine.sendTo("hello_channel_1",
     engine.getConnection(cometConnectionId), "打个招呼");
     //(推送频道(前端可由同样的频道接收), cometConnectionId(建立连接时获取),推送信息内容)

推送到所有页面

    CometEngine engine = CometContext.getInstance().getEngine();
    engine.sendToAll("hello_channel_1", "大家好");
    //不需要传入cometConnectionId

前端代码很简单。页面加载发起连接。在与服务端对应的频道名称下编写回调函数,接收并处理服务端推送的信息。

$(function(){
    JS.Engine.on({
        hello_channel_1: function(msg) {
        //侦听服务端由hello_channel_1频道推送的信息
            console.log("hello:::"+msg)
        },
        kick_channel_1: function(msg) {
            //侦听服务端由kick_channel_1频道推送的信息
            console.log("time:::"+msg);
        },
        start: function(cId,channelList, engine){
            //连接触发的方法
            console.info("连接信息==" + cId);
        },                  
        stop:  function(cause, cId, url, engine){
            //断开触发的方法  
            console.info("断开连接==" + cId);
        }       
    });
    JS.Engine.start('http://127.0.0.1:8080/myspringboot/demo/cometconn','userId=2&password=123456'); 
    /*
        JS.Engine.start("连接地址", "请求参数");
        连接地址与服务端配置保持一致
        注意服务端源码中:
        ServletRegistrationBean servletRegistrationBean = 
             new ServletRegistrationBean( new CometServlet(),
             "/demo/cometconn");
    */ 
});

我做的demo逻辑是这样的:

  • 一个receive页面向服务端CometServlet地址发起comet请求,触发comet连接事件。JoinListener监听器处理逻辑将cometConnectionId与请求携带的参数userId添加至缓存Map中,通过userId可以实现定向推送。

  • 另一个send页面用于触发推送事件,服务器收到请求后将上传的图片转为Base64代码,根据userId查找相应cometConnectionId,调用CometFaceSendUtil工具类向指定页面发起推送。

  • receive页面关闭时将触发离开事件,在LeftListener中需将相应的cometConnectionId从缓存中移除。

    • 整个demo实现了从send页面上传的图片在receive页面的主动展示。

PS:

前端展示图片的时候发生了一点小问题。从控制台看前端已收到信息,但显示图片失败。经过检查发现推送的Base64编码不完整。

查看comet4j.js源码发现comet连接使用的是ajax机制发起GET请求。GET请求长度有限,导致信息被截短。解决方法是:将comet4j.js中三处GET请求(分别位于start,revival,send函数中)修改为POST。

目前还有一个tomcat8兼容性的问题未解决。在maven中引入tomcat7补丁jar包的问题不能解决启动报ClassNotFoundException问题。还有待于各位大牛支招。

 类似资料: