一、什么是pushlet?
1.pushlet推送是一种将java后台数据推送到web页面的框架技术,实现了comet。
2.comet是一个用于描述客户端和服务器之间交互的术语,即使用长期保持的http连接来在连接保持畅通的情况下支持客户端和服务器间的事件驱动的通信
3.传统的web系统的工作流程是客户端发出请求,服务器端进行响应,而comet则是在现有技术的基础上,实现服务器数据、事件等快速push到客户端,所以会出现一个术语”服务器推“技术。
二、单机
1.项目采用技术
1).后端SSH框架
2).前端jsp
3).接口提供方c#
2.流程
对jsp列表页面的数据处理时调用c#端,当c#端处理完之后会调用java端接口,java端通过接口创建事件发布消息,jsp列表页面会做后端事件监听做数据更新操作。
3.项目结构
将pushlet.jar放到lib目录中,引入到工程,在resources资源目录下添加pushlet.properties和sources.properties配置文件,如下图:
pushlet.properties:pushlet初始化配置
source.properties:消息源配置
4.web.xml配置
<servlet>
<servlet-name>pushlet</servlet-name>
<servlet-class>
nl.justobjects.pushlet.servlet.Pushlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>pushlet</servlet-name>
<url-pattern>/pushlet.srv</url-pattern>
</servlet-mapping>
5.pushlet.properties
#
# Pushlet configuration.
# Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF.
#
# $Id: pushlet.properties,v 1.13 2007/12/07 12:57:40 justb Exp $
#
#
#
#
config.version=1.0.2
#
# CLASS FACTORY SPECIFICATION
#
# Change these if you want to override any of the core classes
# within the Pushlet framework with your own custom classes.
#
# Examples:
# - custom SessionManager for authorisation
# - maintain lists of active subjects (topics)
# - send events on subscription
# - plug in custom logging like log4j
# Note that you must maintain the semantics of each class !
# Below are the default properties for the core classes.
controller.class=nl.justobjects.pushlet.core.Controller
dispatcher.class=nl.justobjects.pushlet.core.Dispatcher
logger.class=nl.justobjects.pushlet.util.Log4jLogger
# logger.class=nl.justobjects.pushlet.util.DefaultLogger
#sessionmanager.class=nl.justobjects.pushlet.core.SessionManager
sessionmanager.class=com.yudu.pushlet.WfSessionManager
session.class=nl.justobjects.pushlet.core.Session
subscriber.class=nl.justobjects.pushlet.core.Subscriber
subscription.class=nl.justobjects.pushlet.core.Subscription
# sessionmanager.maxsessions=200
#
# DISPATCHER
#
# TODO: allow properties to be maintained in
# a user dir
# config.redirect=/etc/pushlet.properties
#
# LOGGING
#
# log level (trace(6) debug(5) info (4), warn(3), error(2), fatal(1))
# default is info(4)
log.level=4
#
# LOCAL EVENT SOURCES
#
# should local sources be loaded ?
sources.activate=true
#
# SESSION
#
# algoritm to generate session key:
# values: "randomstring" (default) or "uuid".
# session.id.generation=uuid
session.id.generation=randomstring
# length of generated session key when using "randomstring" generation
session.id.size=10
# Overall session lease time in minutes
# Mainly used for clients that do not perform
# listening, e.g. when publishing only.
session.timeout.mins=5
#
# EVENT QUEUE
#
# Properties for per-client data event queue
# Size for
queue.size=24
queue.read.timeout.millis=20000
queue.write.timeout.millis=20
#
# LISTENING MODE
#
# You may force all clients to use pull mode
# for scalability
listen.force.pull.all=false
#
# Comma-separated list of User Agent substrings.
# Force these browsers to use pull mode, since they
# don't support JS streaming, matching is done using
# String.indexOf() with lowercased agent strings
# use multiple criteria with &.
#
listen.force.pull.agents=safari
#
# PULL MODE
#
# time server should wait on refresing pull client 45000
pull.refresh.timeout.millis=30000
# minimum/maximum wait time client should wait before refreshing
# server provides a random time between these values
pull.refresh.wait.min.millis=2000
pull.refresh.wait.max.millis=3000
#
# POLL MODE
#
# time server should wait on refresing poll client
poll.refresh.timeout.millis=6000
# minimum/maximum wait time client should wait before refreshing
# server provides a random time between these values 6000~10000
poll.refresh.wait.min.millis=3000
poll.refresh.wait.max.millis=6000
6.source.properties以及消息源类
#
# Properties file for EventSource objects to be instantiated.
#
# Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF.
#
# $Id: sources.properties,v 1.2 2007/11/10 14:12:16 justb Exp $
#
# Each EventSource is defined as <key>=<classname>
# 1. <key> should be unique within this file but may be any name
# 2. <classname> is the full class name
#
#
# Define Pull Sources here. These classes must be derived from
# nl.justobjects.pushlet.core.EventPullSource
# Inner classes are separated with a $ sign from the outer class.
#source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSource
#source2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSource
#source3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSource
#source4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSource
#source5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSource
#source6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource
# $前是消息源类,$后是消息源类的内部类
source1=com.yudu.pushlet.YuduBpmEventPullSources$BpmEventSource
# TO BE DONE IN NEXT VERSION
# define Push Sources here. These must implement the interface
# nl.justobjects.pushlet.core.EventSource
YuduBpmEventPullSources.java
package com.yudu.pushlet;
import java.io.Serializable;
import nl.justobjects.pushlet.core.Event;
import nl.justobjects.pushlet.core.EventPullSource;
public class YuduBpmEventPullSources implements Serializable {
private static final long serialVersionUID = 4677762094577114268L;
public static class BpmEventSource extends EventPullSource {
@Override
protected long getSleepTime() {
return 3000;
}
@Override
protected Event pullEvent() {
Event event =Event.createDataEvent("/yudu/ydbpm");
event.setField("ydbpm","ping");
return event;
}
}
}
7.session管理类
package com.yudu.pushlet;
import nl.justobjects.pushlet.core.Event;
import nl.justobjects.pushlet.core.Session;
import nl.justobjects.pushlet.core.SessionManager;
import nl.justobjects.pushlet.util.PushletException;
public class WfSessionManager extends SessionManager {
/*
* 创建session 将用户id或者用户登录的sessionid
* by oklin 2016
* */
public Session createSession(Event anEvent) throws PushletException {
// Trivial
//return Session.create(createSessionId());
String userId = anEvent.getField("uid", createSessionId());
Session session = SessionManager.getInstance().getSession(userId);
if(session != null)
{
//System.out.print("存在session:"+userId);
session.stop();
//session = SessionManager.getInstance().getSession(userId);
//return session;
}
return Session.create(userId);
}
}
8.todolist.jsp
<%@ include file="../common/include.jsp"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.ResourceBundle" %>
<%
String basePath = path+"/";
ResourceBundle res = ResourceBundle.getBundle("ydbpm");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="${path}/css/table.css" rel="stylesheet" type="text/css" />
<link href="${path}/css/base.css" rel="stylesheet" type="text/css" />
<link href="${path}/css/document/documentSearch.css" rel="stylesheet"
type="text/css" />
<link href="${path}/tools/jquery-ui/css/smoothness/jquery-ui-1.10.3.custom.min.css" rel="stylesheet" type="text/css" />
<link href="${path}/tools/ztree/css/zTreeStyle/zTreeStyle.css"
rel="stylesheet" type="text/css" />
<link href="${path}/css/meetingissues/draft.css" rel="stylesheet"
type="text/css" />
<style type="text/css">
.t-main tbody tr:hover {
background: #DCE8FA
}
.isFlowEnding {
background: #B5CEF4;
font-weight: bold;
}
.isFlowEnding td {
color: red;
}
.isTaskEnding {
font-weight: bold;
}
</style>
<script type="text/javascript" src="<%=path%>/js/pushlet/ajax-pushlet-client.js"></script>
<script type="text/javascript">
PL._init(); //对pushlet的初始化,触发web.xml中的servlet。
PL.userId='${userInfo.userInfoId}';
PL.joinListen('/yudu/ydbpm'); //这里的监听的主题,必须在sources.properties中配置的对象中声明这个主题。
function onData(event) {
if(event.get("YUDU_WF_MSG"))
{
switch(event.get("YUDU_WF_MSG")){
case "WF_SUBMIT":
case "WF_BACK":
case "WF_QUIT":
location.href=location.href;//location.reload();
break;
}
}
}
</script>
<title></title>
</head>
<body>
<script src="${path}/tools/jquery-ui/js/jquery-ui-1.10.3.custom.min.js" type="text/javascript" charset="UTF-8"></script>
<script src="${path}/js/document/todolist.js" type="text/javascript" charset="UTF-8"></script>
</body>
</html>
9.todolist.js核心代码
var restate=basePath+"yudu/submitdone.action";//调用java端,完成服务端消息推送
//在调用中间件url中嵌套了java端url
var url = protocol+"/Viewer.axd?op=forms&uid=" + uid + "&bid=" + bid+"&id="+id+"&catalog=0&ext="+escape(restate)+"&extp="+escape("uid="+uid+"_3");
10.pushlet处理action
package com.yudu.pushlet;
import org.apache.commons.lang.StringUtils;
import com.ccc.base.action.BaseAction;
import com.ccc.system.model.UserInfo;
import com.yudu.pushlet.PushletUtil;;
/*
* 发送消息
*
* by oklin 2016-4-22
* */
public class SendMsgAction extends BaseAction {
/**
*
*/
private static final long serialVersionUID = 1930946576605702484L;
public static final String WF_SUBMIT_MSG="WF_SUBMIT"; //提交 1
public static final String WF_BACK_MSG="WF_BACK"; //回退 2
public static final String WF_QUIT_MSG="WF_QUIT"; //退出 3
public static final String WF_ERROR_MSG="WF_ERROR"; //发生错误 4
public static final String WF_UNKNOWN_MSG="WF_UNKNOWN"; //未知错误
/**
* 无法获取session
* 所有在一个参数的值中将多个参数的值拼接起来
* @return
*/
public String submitDone() {
try {
String sessionId = this.getRequest().getParameter("uid").split("_")[0];
String msgt = this.getRequest().getParameter("uid").split("_")[1];
if(StringUtils.isNotBlank(msgt))
{
int msgType = Integer.parseInt( msgt);
switch(msgType)
{
case 1:
PushletUtil.sendMessage(sessionId,WF_SUBMIT_MSG);
break;
case 2:
PushletUtil.sendMessage(sessionId,WF_BACK_MSG);
break;
case 3:
PushletUtil.sendMessage(sessionId,WF_QUIT_MSG);
break;
case 4:
PushletUtil.sendMessage(sessionId,WF_ERROR_MSG);
break;
default:
PushletUtil.sendMessage(sessionId,WF_UNKNOWN_MSG);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}
}
10.pushlet工具类
package com.yudu.pushlet;
import nl.justobjects.pushlet.core.Dispatcher;
import nl.justobjects.pushlet.core.Event;
import nl.justobjects.pushlet.core.SessionManager;
import org.apache.commons.lang.StringUtils;
public class PushletUtil {
private static String PLATFORM_MESSAGE = "YUDU_WF_MSG";
/**
* 发送消息至客户端
* @param sessionId
* @param msg
*/
public static void sendMessage(String sessionId, String msg){
if(StringUtils.isNotBlank(sessionId) && SessionManager.getInstance().hasSession(sessionId)){
Event event = Event.createDataEvent("/yudu/ydbpm");
event.setField(PLATFORM_MESSAGE, msg);
Dispatcher.getInstance().unicast(event, sessionId);
}
}
/**
* 广播至所有在线客户端
* @param msg
*/
public static void sendBroadcast(String msg){
Event event = Event.createDataEvent("/yudu/ydbpm");
event.setField(PLATFORM_MESSAGE, msg);
Dispatcher.getInstance().broadcast(event);
}
}
三、集群
nginx做负载均衡,反向代理两个服务,前端页面无需获取真实ip,后端亦如此。即可实现集群下服务端消息推送