从官网Pushlet下载最新的版本也就是2.0.4,下载的压缩包里面包含了lib,src,webapps几个主要文件夹,lib下面有开发所用到的pushlet.jar,src下面是源代码,webapps下面有个pushlet.war可以直接丢进tomcat跑起来,里面演示了pushlet提供的各种功能。
使用pushlet时需要配置一个servlet,用来接受pushlet的浏览器请求并返回特定的响应配置前端的js。
servlet配置如下:
<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>
<!-- pattern 与js中pushletURL 对应 -->
<url-pattern>/pushlet.srv</url-pattern>
</servlet-mapping>
需要注意的是"/pushlet.srv"是在js中写死的,pattern里面设置的url如果要修改js也要修改
public void init() throws ServletException {
try {
// Load configuration (from classpath or WEB-INF root path)
String webInfPath = getServletContext().getRealPath("/") + "/WEB-INF";
Config.load(webInfPath);
Log.init();
// Start
Log.info("init() Pushlet Webapp - version=" + Version.SOFTWARE_VERSION + " built=" + Version.BUILD_DATE);
// Start session manager
SessionManager.getInstance().start();
// Start event Dispatcher
Dispatcher.getInstance().start();
if (Config.getBoolProperty(Config.SOURCES_ACTIVATE)) {
EventSourceManager.start(webInfPath);
} else {
Log.info("Not starting local event sources");
}
} catch (Throwable t) {
throw new ServletException("Failed to initialize Pushlet framework " + t, t);
}
}
其中Config.load方法是获取pushlet的全局配置,方法里面它会去classes下读取pushlet.properties,如果读取不到才会到WEB-INF下面读取,配置信息会保存在一个Properties对象里面供其它类使用。
Config是个工厂类,
/**
<span style="white-space:pre"> </span> * Factory method: create object from property denoting class name.
*
* @param aClassNameProp property name e.g. "session.class"
* @return a Class object denoted by property
* @throws PushletException when class cannot be instantiated
*/
public static Class getClass(String aClassNameProp, String aDefault) throws PushletException {
// Singleton + factory pattern: create object instance
// from configured class name
String clazz = (aDefault == null ? getProperty(aClassNameProp) : getProperty(aClassNameProp, aDefault));
try {
return Class.forName(clazz);
} catch (ClassNotFoundException t) {
// Usually a misconfiguration
throw new PushletException("Cannot find class for " + aClassNameProp + "=" + clazz, t);
}
}
方法返回一个Class对象,其中入参aClassNameProp为properties中配置的一个key,通过这个key获取value(类的全路径)后返回一个Class对象,代码里面很多地方都是使用了这里的工厂模式,看一下SessionManager中的应用:
/**
* Singleton pattern: single instance.
*/
private static SessionManager instance;
static {
// Singleton + factory pattern: create single instance
// from configured class name
try {
instance = (SessionManager) Config.getClass(SESSION_MANAGER_CLASS, "nl.justobjects.pushlet.core.SessionManager").newInstance();
Log.info("SessionManager created className=" + instance.getClass());
} catch (Throwable t) {
Log.fatal("Cannot instantiate SessionManager from config", t);
}
}
public static final String SESSION_MANAGER_CLASS = "sessionmanager.class";
在pushlet.properties中:
sessionmanager.class=nl.justobjects.pushlet.core.SessionManager
SessionManager.getInstance()返回一个单例对象,这里并没有通过构造函数初始化而是像上面那样获取,这样的好处是扩展性好,可以在pushlet.properties中改掉sessionmanager.class,使用自定义的SessionManager实现其它功能,比如我在做单点推送的时候就用到了自己扩展的SessionManager,后面例子中会详细介绍为什么要这样修改。
SessionManager:会话管理,在pushlet中每一个客户端的都会生成一个Session(id唯一)并保存在SessionManager中,这个Session跟浏览器HttpSession意图相似用以保持浏览器跟pushlet server的通信
SessionManager.getInstance().start(); 会启动一个TimerTask,每隔一分钟会检测所有Session是否失效,每个Session会保存一个timeToLive (存活时间),这个也可以在pushlet.properties中配置默认是5分钟,当浏览器发送新的请求时会重置timeToLive为默认值,也就是说如果5分钟内没有收到浏览器请求则此Session过期会做一系列操作。
Dispatcher.getInstance().start();只是一些初始化,做的事情不多。里面有个内部类,当调用multicast、unicast等发布事件时都会委托到这个内部类中(为什么这样设计我也没想明白)。
if (Config.getBoolProperty(Config.SOURCES_ACTIVATE)) {
EventSourceManager.start(webInfPath);
} else {
Log.info("Not starting local event sources");
}
这里判断配置中的
# should local sources be loaded ?
sources.activate=false
是否为true,true时会去读取sources.properties文件,启动定时推送(网上例子很多)。默认是true,也就是默认必须有 sources.properties文件,否则启动servlet报错。
到此init方法结束。
doGet,doPost都会把request里面的参数封装到一个Event里面,最后调用doRequest:
/**
* Generic request handler (GET+POST).
*/
protected void doRequest(Event anEvent, HttpServletRequest request, HttpServletResponse response) {
// Must have valid event type.
String eventType = anEvent.getEventType();
try {
// Get Session: either by creating (on Join eventType)
// or by id (any other eventType, since client is supposed to have joined).
Session session = null;
if (eventType.startsWith(Protocol.E_JOIN)) {
// Join request: create new subscriber
session = SessionManager.getInstance().createSession(anEvent);
String userAgent = request.getHeader("User-Agent");
if (userAgent != null) {
userAgent = userAgent.toLowerCase();
} else {
userAgent = "unknown";
}
session.setUserAgent(userAgent);
} else {
// Must be a request for existing Session
// Get id
String id = anEvent.getField(P_ID);
// We must have an id value
if (id == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No id specified");
Log.warn("Pushlet: bad request, no id specified event=" + eventType);
return;
}
// We have an id: get the session object
session = SessionManager.getInstance().getSession(id);
// Check for invalid id
if (session == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid or expired id: " + id);
Log.warn("Pushlet: bad request, no session found id=" + id + " event=" + eventType);
return;
}
}
// ASSERTION: we have a valid Session
// Let Controller handle request further
// including exceptions
Command command = Command.create(session, anEvent, request, response);
session.getController().doCommand(command);
} catch (Throwable t) {
// Hmm we should never ever get here
Log.warn("Pushlet: Exception in doRequest() event=" + eventType, t);
t.printStackTrace();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
当eventType为join、join_listen时代表浏览器第一次请求,会创建Session:
/**
* Create new Session (but add later).
*/
public Session createSession(Event anEvent) throws PushletException {
// Trivial
return Session.create(createSessionId());
}
createSessionId()会参数一个随机字符串(随机且不重复)后调用Session.create方法,Session源码如下:
// Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
// Distributable under LGPL license. See terms of license at gnu.org.
package nl.justobjects.pushlet.core;
import nl.justobjects.pushlet.util.Log;
import nl.justobjects.pushlet.util.PushletException;
/**
* Represents client pushlet session state.
*
* @author Just van den Broecke - Just Objects ©
* @version $Id: Session.java,v 1.8 2007/11/23 14:33:07 justb Exp $
*/
public class Session implements Protocol, ConfigDefs {
private Controller controller;
private Subscriber subscriber;
private String userAgent;
private long LEASE_TIME_MILLIS = Config.getLongProperty(SESSION_TIMEOUT_MINS) * 60 * 1000;
private volatile long timeToLive = LEASE_TIME_MILLIS;
public static String[] FORCED_PULL_AGENTS = Config.getProperty(LISTEN_FORCE_PULL_AGENTS).split(",");
private String address = "unknown";
private String format = FORMAT_XML;
private String id;
/**
* Protected constructor as we create through factory method.
*/
protected Session() {
}
/**
* Create instance through factory method.
*
* @param anId a session id
* @return a Session object (or derived)
* @throws PushletException exception, usually misconfiguration
*/
public static Session create(String anId) throws PushletException {
Session session;
try {
session = (Session) Config.getClass(SESSION_CLASS, "nl.justobjects.pushlet.core.Session").newInstance();
} catch (Throwable t) {
throw new PushletException("Cannot instantiate Session from config", t);
}
// Init session
session.id = anId;
session.controller = Controller.create(session);
session.subscriber = Subscriber.create(session);
return session;
}
/**
* Return (remote) Subscriber client's IP address.
*/
public String getAddress() {
return address;
}
/**
* Return command controller.
*/
public Controller getController() {
return controller;
}
/**
* Return Event format to send to client.
*/
public String getFormat() {
return format;
}
/**
* Return (remote) Subscriber client's unique id.
*/
public String getId() {
return id;
}
/**
* Return subscriber.
*/
public Subscriber getSubscriber() {
return subscriber;
}
/**
* Return remote HTTP User-Agent.
*/
public String getUserAgent() {
return userAgent;
}
/**
* Set address.
*/
protected void setAddress(String anAddress) {
address = anAddress;
}
/**
* Set event format to encode.
*/
protected void setFormat(String aFormat) {
format = aFormat;
}
/**
* Set client HTTP UserAgent.
*/
public void setUserAgent(String aUserAgent) {
userAgent = aUserAgent;
}
/**
* Decrease time to live.
*/
public void age(long aDeltaMillis) {
timeToLive -= aDeltaMillis;
}
/**
* Has session timed out?
*/
public boolean isExpired() {
return timeToLive <= 0;
}
/**
* Keep alive by resetting TTL.
*/
public void kick() {
timeToLive = LEASE_TIME_MILLIS;
}
public void start() {
SessionManager.getInstance().addSession(this);
}
public void stop() {
subscriber.stop();
SessionManager.getInstance().removeSession(this);
}
/**
* Info.
*/
public void info(String s) {
Log.info("S-" + this + ": " + s);
}
/**
* Exceptional print util.
*/
public void warn(String s) {
Log.warn("S-" + this + ": " + s);
}
/**
* Exceptional print util.
*/
public void debug(String s) {
Log.debug("S-" + this + ": " + s);
}
public String toString() {
return getAddress() + "[" + getId() + "]";
}
}
/*
* $Log: Session.java,v $
* Revision 1.8 2007/11/23 14:33:07 justb
* core classes now configurable through factory
*
* Revision 1.7 2005/02/28 15:58:05 justb
* added SimpleListener example
*
* Revision 1.6 2005/02/28 12:45:59 justb
* introduced Command class
*
* Revision 1.5 2005/02/28 09:14:55 justb
* sessmgr/dispatcher factory/singleton support
*
* Revision 1.4 2005/02/25 15:13:01 justb
* session id generation more robust
*
* Revision 1.3 2005/02/21 16:59:08 justb
* SessionManager and session lease introduced
*
* Revision 1.2 2005/02/21 12:32:28 justb
* fixed publish event in Controller
*
* Revision 1.1 2005/02/21 11:50:46 justb
* ohase1 of refactoring Subscriber into Session/Controller/Subscriber
*
*
*/
// Init session
session.id = anId;
session.controller = Controller.create(session);
session.subscriber = Subscriber.create(session);
同时创建Controller跟Subscriber对象, 它们的create都使用了同样的
Config提供的工厂方法创建一个实例,并设置session属性为传入的session,它们跟Session都相互引用,
创建Session同时会获取请求头中的
User-Agent,记录浏览器特征(id,firefox,chrome...),有些浏览器不支持js流推送时会使用ajax轮询方式。可以看到Session有个id属性, 就是SessionManager里产生的随机字符串,这个id会被传回浏览器,浏览器在后续的pushlet请求中都会带着这个id,就像doRequest里面的判断一样,当不是join或者join_listen时 会主动获取sessionId,并以此获取Session,如果没有则请求失败。
拿到Session后:
// ASSERTION: we have a valid Session
// Let Controller handle request further
// including exceptions
Command command = Command.create(session, anEvent, request, response);
session.getController().doCommand(command);
封装一个Command对象交由Controller处理这次请求。
public void doCommand(Command aCommand)
{
try
{
this.session.kick();
this.session.setAddress(aCommand.httpReq.getRemoteAddr());
debug("doCommand() event=" + aCommand.reqEvent);
String eventType = aCommand.reqEvent.getEventType();
if (eventType.equals("refresh")) {
doRefresh(aCommand);
} else if (eventType.equals("subscribe")) {
doSubscribe(aCommand);
} else if (eventType.equals("unsubscribe")) {
doUnsubscribe(aCommand);
} else if (eventType.equals("join")) {
doJoin(aCommand);
} else if (eventType.equals("join-listen")) {
doJoinListen(aCommand);
} else if (eventType.equals("leave")) {
doLeave(aCommand);
} else if (eventType.equals("hb")) {
doHeartbeat(aCommand);
} else if (eventType.equals("publish")) {
doPublish(aCommand);
} else if (eventType.equals("listen")) {
doListen(aCommand);
}
if ((eventType.endsWith("listen")) || (eventType.equals("refresh"))) {
getSubscriber().fetchEvents(aCommand);
} else {
sendControlResponse(aCommand);
}
}
catch (Throwable t)
{
warn("Exception in doCommand(): " + t);
t.printStackTrace();
}
}
首先调用kick重置session的存活时间,然后根据请求中传来的eventType做出相应处理也就是放浏览器写数据。
if ((eventType.endsWith("listen")) || (eventType.equals("refresh"))) {
getSubscriber().fetchEvents(aCommand);
} else {
sendControlResponse(aCommand);
}
listen对应了长链接方式,refresh对应了ajax轮询,所以最后数据的写入都是在Subscriber的fetchEvents方法里做的。
/**
* Get events from queue and push to client.
*/
public void fetchEvents(Command aCommand) throws PushletException {
String refreshURL = aCommand.httpReq.getRequestURI() + "?" + P_ID + "=" + session.getId() + "&" + P_EVENT + "=" + E_REFRESH;
// This is the only thing required to support "poll" mode
if (mode.equals(MODE_POLL)) {
queueReadTimeoutMillis = 0;
refreshTimeoutMillis = Config.getLongProperty(POLL_REFRESH_TIMEOUT_MILLIS);
}
// Required for fast bailout (tomcat)
aCommand.httpRsp.setBufferSize(128);
// Try to prevent caching in any form.
aCommand.sendResponseHeaders();
// Let clientAdapter determine how to send event
ClientAdapter clientAdapter = aCommand.getClientAdapter();
Event responseEvent = aCommand.getResponseEvent();
try {
clientAdapter.start();
// Send first event (usually hb-ack or listen-ack)
clientAdapter.push(responseEvent);
// In pull/poll mode and when response is listen-ack or join-listen-ack,
// return and force refresh immediately
// such that the client recieves response immediately over this channel.
// This is usually when loading the browser app for the first time
if ((mode.equals(MODE_POLL) || mode.equals(MODE_PULL))
&& responseEvent.getEventType().endsWith(Protocol.E_LISTEN_ACK)) {
sendRefresh(clientAdapter, refreshURL);
// We should come back later with refresh event...
return;
}
} catch (Throwable t) {
bailout();
return;
}
Event[] events = null;
// Main loop: as long as connected, get events and push to client
long eventSeqNr = 1;
while (isActive()) {
// Indicate we are still alive
lastAlive = Sys.now();
// Update session time to live
session.kick();
// Get next events; blocks until timeout or entire contents
// of event queue is returned. Note that "poll" mode
// will return immediately when queue is empty.
try {
// Put heartbeat in queue when starting to listen in stream mode
// This speeds up the return of *_LISTEN_ACK
if (mode.equals(MODE_STREAM) && eventSeqNr == 1) {
eventQueue.enQueue(new Event(E_HEARTBEAT));
}
events = eventQueue.deQueueAll(queueReadTimeoutMillis);
} catch (InterruptedException ie) {
warn("interrupted");
bailout();
}
// Send heartbeat when no events received
if (events == null) {
events = new Event[1];
events[0] = new Event(E_HEARTBEAT);
}
// ASSERT: one or more events available
// Send events to client using adapter
// debug("received event count=" + events.length);
for (int i = 0; i < events.length; i++) {
// Check for abort event
if (events[i].getEventType().equals(E_ABORT)) {
warn("Aborting Subscriber");
bailout();
}
// Push next Event to client
try {
// Set sequence number
events[i].setField(P_SEQ, eventSeqNr++);
// Push to client through client adapter
clientAdapter.push(events[i]);
} catch (Throwable t) {
bailout();
return;
}
}
// Force client refresh request in pull or poll modes
if (mode.equals(MODE_PULL) || mode.equals(MODE_POLL)) {
sendRefresh(clientAdapter, refreshURL);
// Always leave loop in pull/poll mode
break;
}
}
}
这里面可以清楚的看,基于不同的方式数据写入也不同
// Let clientAdapter determine how to send event
ClientAdapter clientAdapter = aCommand.getClientAdapter();
获取ClientAdapter实现,共有三中实现,长链接方式使用BrowserAdapter,ajax轮询方式使用XMLAdapter。
很明显,长链接方式时while循环将不会结束,除非浏览器发送了leave请求使isActive()为false,或者关掉浏览器。
而轮询方式时会在sendRefresh(clientAdapter, refreshURL);后跳出循环,请求也就结束了。
网上大部分都是ajax轮询的例子,我这里说下长链接方式的使用,其实也很简单,除了jar包外,还需要js-pushlet-client.js,和js-pushlet-net.html ,在官网下载的压缩包里都有。
页面引入js后,需要执行p_embed(); 这个在js-pushlet-client.js里面有定义,就是去加载js-pushlet-net.html。
这里面可能需要根据你自己项目的结构配置相应的路径,js-pushlet-net.html复制发送pushlet请求,并保持长链接,写入的js代码会回调到js-pushlet-client.js中去,最后调用到你自己编写的事件回调如onData等。
调用订阅:p_join_listen('subject'); 这里会使用加载的iframe往server发请求,建立session等如上文描述过程。
这是效果图
发布通知:
Event event = Event.createDataEvent("");
event.setField("test", "test");
Dispatcher.getInstance().multicast(event);
/**
* Routes Events to Subscribers.
*
* @author Just van den Broecke - Just Objects ©
* @version $Id: Dispatcher.java,v 1.9 2007/12/04 13:55:53 justb Exp $
*/
public class Dispatcher implements Protocol, ConfigDefs {
/**
* Singleton pattern: single instance.
*/
private static Dispatcher instance;
protected SessionManagerVisitor sessionManagerVisitor;
static {
try {
instance = (Dispatcher) Config.getClass(DISPATCHER_CLASS, "nl.justobjects.pushlet.core.Dispatcher").newInstance();
Log.info("Dispatcher created className=" + instance.getClass());
} catch (Throwable t) {
Log.fatal("Cannot instantiate Dispatcher from config", t);
}
}
/**
* Singleton pattern with factory method: protected constructor.
*/
protected Dispatcher() {
}
/**
* Singleton pattern: get single instance.
*/
public static Dispatcher getInstance() {
return instance;
}
/**
* Send event to all subscribers.
*/
public synchronized void broadcast(Event anEvent) {
try {
// Let the SessionManager loop through Sessions, calling
// our Visitor Method for each Session. This is done to guard
// synchronization with SessionManager and to optimize by
// not getting an array of all sessions.
Object[] args = new Object[2];
args[1] = anEvent;
Method method = sessionManagerVisitor.getMethod("visitBroadcast");
SessionManager.getInstance().apply(sessionManagerVisitor, method, args);
} catch (Throwable t) {
Log.error("Error calling SessionManager.apply: ", t);
}
}
/**
* Send event to subscribers matching Event subject.
*/
public synchronized void multicast(Event anEvent) {
try {
// Let the SessionManager loop through Sessions, calling
// our Visitor Method for each Session. This is done to guard
// synchronization with SessionManager and to optimize by
// not getting an array of all sessions.
Method method = sessionManagerVisitor.getMethod("visitMulticast");
Object[] args = new Object[2];
args[1] = anEvent;
SessionManager.getInstance().apply(sessionManagerVisitor, method, args);
} catch (Throwable t) {
Log.error("Error calling SessionManager.apply: ", t);
}
}
/**
* Send event to specific subscriber.
*/
public synchronized void unicast(Event event, String aSessionId) {
// Get subscriber to send event to
Session session = SessionManager.getInstance().getSession(aSessionId);
if (session == null) {
Log.warn("unicast: session with id=" + aSessionId + " does not exist");
return;
}
// Send Event to subscriber.
session.getSubscriber().onEvent((Event) event.clone());
}
/**
* Start Dispatcher.
*/
public void start() throws PushletException {
Log.info("Dispatcher started");
// Create callback for SessionManager visits.
sessionManagerVisitor = new SessionManagerVisitor();
}
/**
* Stop Dispatcher.
*/
public void stop() {
// Send abort control event to all subscribers.
Log.info("Dispatcher stopped: broadcast abort to all subscribers");
broadcast(new Event(E_ABORT));
}
/**
* Supplies Visitor methods for callbacks from SessionManager.
*/
private class SessionManagerVisitor {
private final Map visitorMethods = new HashMap(2);
SessionManagerVisitor() throws PushletException {
try {
// Setup Visitor Methods for callback from SessionManager
// This is a slight opitmization over creating Method objects
// on each invokation.
Class[] argsClasses = {Session.class, Event.class};
visitorMethods.put("visitMulticast", this.getClass().getMethod("visitMulticast", argsClasses));
visitorMethods.put("visitBroadcast", this.getClass().getMethod("visitBroadcast", argsClasses));
} catch (NoSuchMethodException e) {
throw new PushletException("Failed to setup SessionManagerVisitor", e);
}
}
/**
* Return Visitor Method by name.
*/
public Method getMethod(String aName) {
return (Method) visitorMethods.get(aName);
}
/**
* Visitor method called by SessionManager.
*/
public void visitBroadcast(Session aSession, Event event) {
aSession.getSubscriber().onEvent((Event) event.clone());
}
/**
* Visitor method called by SessionManager.
*/
public void visitMulticast(Session aSession, Event event) {
Subscriber subscriber = aSession.getSubscriber();
Event clonedEvent;
Subscription subscription;
// Send only if the subscriber's criteria
// match the event.
if ((subscription = subscriber.match(event)) != null) {
// Personalize event
clonedEvent = (Event) event.clone();
// Set subscription id and optional label
clonedEvent.setField(P_SUBSCRIPTION_ID, subscription.getId());
if (subscription.getLabel() != null) {
event.setField(P_SUBSCRIPTION_LABEL, subscription.getLabel());
}
subscriber.onEvent(clonedEvent);
}
}
}
}
所有通知的发布,都是在SessionManagerVisitor里完成,start方法中实例化了这个对象(servlet启动时调用)
broadcast向所有订阅者发布消息,无需匹配subject;
multicast向所有匹配了subject的订阅者发布消息;
subject 支持级联和多事件
比如:
p_join_listen('/subject1, /subject2');//这里'/'不是必须只要是字符串就行,官网的demo都是带'/'
订阅这个事件时,下面几个通知都会被订阅
"/subject1";"/subject2";"/subject1234"; "/subject232";
unicast向指定的订阅者发布通知,需要传入sessionId;
上文中已经详述默认生成Session时都会生成一个唯一且随机的id,每个浏览器对以一个sessinId,这个id会贯穿请求始终。
如果我要向特定的浏览器的推送消息,就必须获取到这个sessionId, 因此采用随机生成的id实现单点推送很不方便(要实现是可以的,比如在前端获取到sessionId后系统中用户标识(userId, userName)进行绑定,一一关联后,如果要向特定用户推送可以根据用户标识获取绑定的sessionId,进行unicast)。
如果在生成Session的时候就使用userId作为Session的id,那么就可以在任何地方通过userId进行推送。通过扩展SessionManager可以实现这个功能。
session = SessionManager.getInstance().createSession(anEvent);
createSession的本来实现是这样的:
/**
* Create new Session (but add later).
*/
public Session createSession(Event anEvent) throws PushletException {
// Trivial
return Session.create(createSessionId());
}
我们要重写这个方法, 前文已经提到可以在pushlet.properties中指定类名来让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;
/**
* 重写createSession方法,产生与系统用户绑定的Session
* @author main 2014年4月9日 下午2:03:39
* @version V1.0
*/
public class CustomSessionManager extends SessionManager {
@Override
public Session createSession(Event anEvent) throws PushletException {
//从传入的anEvent中获取usId字段用作sessionId, 如果没有(未登录用户)则随机生成
String sessionId = anEvent.getField("usId", createSessionId());
return Session.create(sessionId);
}
}
并修改配置:
sessionmanager.class=com.xxx.xxx.CustomSessionManager
js-pushlet-client.js中修改少许代码在请求发送时传入usId参数即可。
然后通过调用Dispatcher.getInstance().unicast(event,1); 实现往id为1的用户推送消息。
写的不好,大家见谅。希望对新手有所帮助。