**
**
前段时间项目升级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上挖到了我想要的东西。地址后贴。
**
**
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从缓存中移除。
PS:
前端展示图片的时候发生了一点小问题。从控制台看前端已收到信息,但显示图片失败。经过检查发现推送的Base64编码不完整。
查看comet4j.js源码发现comet连接使用的是ajax机制发起GET请求。GET请求长度有限,导致信息被截短。解决方法是:将comet4j.js中三处GET请求(分别位于start,revival,send函数中)修改为POST。
目前还有一个tomcat8兼容性的问题未解决。在maven中引入tomcat7补丁jar包的问题不能解决启动报ClassNotFoundException问题。还有待于各位大牛支招。