Pushlet2.0.4源码分析及实例(iframe流 + ajax轮询)

从官网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 &copy;
 * @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 &copy;
 * @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的用户推送消息。


写的不好,大家见谅。希望对新手有所帮助。











  • 2
    点赞
  • 1
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

Pushlet 是一个开源的 Comet 框架,Pushlet 使用了观察者模式:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。 server端向浏览器client发送通知这种通讯模式在J2EE应用中很常见,通常使 Pushlet服务器端Java类UML图 Pushlet服务器端Java类UML图 用采用RMI、CORBA或者自定义TCP/IP信息的applet来实现。这些技术往往由于复杂而产生诸多不利之处:技术难以实现、存在防火墙限制(因为需要打开非HTTP的通讯端口)、需要额外的server开发和维护。并且除了刷新整个页面或者完全采用applet展示内容之外,很难找到别的方法将client端applet的状态和浏览器的页面内容集成在一起。 Pushlet是一种comet实现:在Servlet机制下,数据从server端的Java对象直接推送(push)到(动态)HTML页面,而无需任何Java applet或者插件的帮助。它使server端可以周期性地更新client的web页面,这与传统的request/response方式相悖。浏览器client为兼容JavaScript1.4版本以上的浏览器(如Internet Explorer、FireFox),并使用JavaScript/Dynamic HTML特性。而底层实现使用一个servlet通过Http连接到JavaScript所在的浏览器,并将数据推送到后者。有关JavaScript版本的知识请参看Mozilla开发中心提供的《JavaScript核心参考》和Stephen Chapman编写的《What Version of Javascript》。 这种机制是轻量级的,它使用server端的servlet连接管理、线程工具、javax.servlet API,并通过标准Java特性中Object的wait()和notify()实现的生产者/消费者机制。原则上,Pushlet框架能够运行在任何支持servlet的server上、防火墙的后面。当在client中使用JavaScript/DHTML时,Pushlet提供了通过脚本快速建立应用、使用HTML/CSS特性集成和布局新内容的便利方法。
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值