自己准备做一个小游戏,租个云服务,然后挂在网上,可以跟同学一起玩,不过首先布置的是,这个游戏是否能实现,多人在线网页游戏,考虑到是否能够实时查询,在网上借鉴了下聊天原理,http长连接,搜索到pushlet可以满足要求,特来学习
首先网上下载了jar包,根据http://blog.csdn.net/lupeng0527/article/details/19019585提到,可以自制maven jar,所以学着达成jar包了
借鉴这个人写的文章:http://www.cnblogs.com/linjiqin/archive/2011/12/30/2307788.html
一、comet基本概念
1.comet是一个用于描述客户端和服务器之间交互的术语,即使用长期保持的http连接来在连接保持畅通的情况下支持客户端和服务器间的事件驱动的通信。
2.传统的web系统的工作流程是客户端发出请求,服务器端进行响应,而comet则是在现有技术的基础上,实现服务器数据、事件等快速push到客户端,所以会出现一个术语”服务器推“技术。
二、push实现方式
1.原理:
利用jsp/servel技术,在不关闭http流的情况下push数据到客户端浏览器;
2.实现:
基于ajax的长轮询(long-polling)方式
ajax的出现使得javascript可以调用xmlhttprequest对象发出http请求,javascript响应处理函数根据服务器返回的信息对html页面的显示进行更新。使用ajax实现“服务器推”与传统的ajax应用不同之处在于:
1)、服务器端会阻塞请求直到有数据传递或超时才返回。
2)、客户端 javascript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
3)、当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重 新建立连接,客户端会一次把当前服务器端所有的信息取回。
按照上述的博客的确能做到http长连,等有新进展成果重新编辑
只有博客后面有人说道的js或者pushlet.properties找不到的,麻烦看下pushlet的source,里面是有的
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
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
pull.refresh.timeout.millis=45000
# 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=6000
#
# POLL MODE
#
# time server should wait on refresing poll client
poll.refresh.timeout.millis=60000
# minimum/maximum wait time client should wait before refreshing
# server provides a random time between these values
poll.refresh.wait.min.millis=6000
poll.refresh.wait.max.millis=10000
/*
* Pushlet client using AJAX XMLHttpRequest.
*
* DESCRIPTION
* This file provides self-contained support for using the
* Pushlet protocol through AJAX-technology. The XMLHttpRequest is used
* to exchange the Pushlet protocol XML messages (may use JSON in later versions).
* Currently only HTTP GET is used in asynchronous mode.
*
* The Pushlet protocol provides a Publish/Subscribe service for
* simple messages. The Pushlet server provides session management (join/leave),
* subscription management (subscribe/unsubscribe), server originated push
* and publication (publish).
*
* For subscriptions server-push is emulated using a single
* long-lived XMLHttpRequests (Pushlet pull mode) where the server holds the
* request until events arrive for which a session has subscriptions.
* This is thus different from polling. In future versions XML streaming
* may be used since this is currently only supported in Moz-family browsers.
*
* Users should supply global callback functions for the events they are interested in.
* For now see _onEvent() for the specific functions that are called.
* The most important one is onData(). If onEvent() is available that catches
* all events. All callback functions have a single
* argument with a PushletEvent object.
* A future version should provide a more OO (Observer) approach.
*
* EXAMPLES
* PL.join();
* PL.listen();
* PL.subscribe('/temperature');
* // or shorter
* PL.joinListen('/temperature');
* // You provide as callback:
* onData(pushletEvent);
* See examples in the Pushlet distribution (e.g. webapps/pushlet/examples/ajax)
*
* WHY
* IMO using XMLHttpRequest has many advantages over the original JS streaming:
* more stability, no browser busy-bees, better integration with other AJAX frameworks,
* more debugable, more understandable, ...
*
* $Id: ajax-pushlet-client.js,v 1.7 2007/07/27 11:45:08 justb Exp $
*/
/** Namespaced Pushlet functions. */
var PL = {
NV_P_FORMAT: 'p_format=xml-strict',
NV_P_MODE: 'p_mode=pull',
pushletURL: null,
webRoot: null,
sessionId: null,
STATE_ERROR: -2,
STATE_ABORT: -1,
STATE_NULL: 1,
STATE_READY: 2,
STATE_JOINED: 3,
STATE_LISTENING: 3,
state: 1,
/************** START PUBLIC FUNCTIONS **************/
/** Send heartbeat. */
heartbeat: function() {
PL._doRequest('hb');
},
/** Join. */
join: function() {
PL.sessionId = null;
// Streaming is only supported in Mozilla. E.g. IE does not allow access to responseText on readyState == 3
PL._doRequest('join', PL.NV_P_FORMAT + '&' + PL.NV_P_MODE);
},
/** Join, listen and subscribe. */
joinListen: function(aSubject) {
PL._setStatus('join-listen ' + aSubject);
// PL.join();
// PL.listen(aSubject);
PL.sessionId = null;
// Create event URI for listen
var query = PL.NV_P_FORMAT + '&' + PL.NV_P_MODE;
// Optional subject to subscribe to
if (aSubject) {
query = query + '&p_subject=' + aSubject;
}
PL._doRequest('join-listen', query);
},
/** Close pushlet session. */
leave: function() {
PL._doRequest('leave');
},
/** Listen on event channel. */
listen: function(aSubject) {
// Create event URI for listen
var query = PL.NV_P_MODE;
// Optional subject to subscribe to
if (aSubject) {
query = query + '&p_subject=' + aSubject;
}
PL._doRequest('listen', query);
},
/** Publish to subject. */
publish: function(aSubject, theQueryArgs) {
var query = 'p_subject=' + aSubject;
if (theQueryArgs) {
query = query + '&' + theQueryArgs;
}
PL._doRequest('publish', query);
},
/** Subscribe to (comma separated) subject(s). */
subscribe: function(aSubject, aLabel) {
var query = 'p_subject=' + aSubject;
if (aLabel) {
query = query + '&p_label=' + aLabel;
}
PL._doRequest('subscribe', query);
},
/** Unsubscribe from (all) subject(s). */
unsubscribe: function(aSubscriptionId) {
var query;
// If no sid we unsubscribe from all subscriptions
if (aSubscriptionId) {
query = 'p_sid=' + aSubscriptionId;
}
PL._doRequest('unsubscribe', query);
},
setDebug: function(bool) {
PL.debugOn = bool;
},
/************** END PUBLIC FUNCTIONS **************/
// Cross-browser add event listener to element
_addEvent: function (elm, evType, callback, useCapture) {
var obj = PL._getObject(elm);
if (obj.addEventListener) {
obj.addEventListener(evType, callback, useCapture);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent('on' + evType, callback);
return r;
} else {
obj['on' + evType] = callback;
}
},
_doCallback: function(event, cbFunction) {
// Do specific callback function if provided by client
if (cbFunction) {
// Do specific callback like onData(), onJoinAck() etc.
cbFunction(event);
} else if (window.onEvent) {
// general callback onEvent() provided to catch all events
onEvent(event);
}
},
// Do XML HTTP request
_doRequest: function(anEvent, aQuery) {
// Check if we are not in any error state
if (PL.state < 0) {
PL._setStatus('died (' + PL.state + ')');
return;
}
// We may have (async) requests outstanding and thus
// may have to wait for them to complete and change state.
var waitForState = false;
if (anEvent == 'join' || anEvent == 'join-listen') {
// We can only join after initialization
waitForState = (PL.state < PL.STATE_READY);
} else if (anEvent == 'leave') {
PL.state = PL.STATE_READY;
} else if (anEvent == 'refresh') {
// We must be in the listening state
if (PL.state != PL.STATE_LISTENING) {
return;
}
} else if (anEvent == 'listen') {
// We must have joined before we can listen
waitForState = (PL.state < PL.STATE_JOINED);
} else if (anEvent == 'subscribe' || anEvent == 'unsubscribe') {
// We must be listeing for subscription mgmnt
waitForState = (PL.state < PL.STATE_LISTENING);
} else {
// All other requests require that we have at least joined
waitForState = (PL.state < PL.STATE_JOINED);
}
// May have to wait for right state to issue request
if (waitForState == true) {
PL._setStatus(anEvent + ' , waiting... state=' + PL.state);
setTimeout(function() {
PL._doRequest(anEvent, aQuery);
}, 100);
return;
}
// ASSERTION: PL.state is OK for this request
// Construct base URL for GET
var url = PL.pushletURL + '?p_event=' + anEvent;
// Optionally attach query string
if (aQuery) {
url = url + '&' + aQuery;
}
// Optionally attach session id
if (PL.sessionId != null) {
url = url + '&p_id=' + PL.sessionId;
if (anEvent == 'p_leave') {
PL.sessionId = null;
}
}
PL.debug('_doRequest', url);
PL._getXML(url, PL._onResponse);
// uncomment to use synchronous XmlHttpRequest
//var rsp = PL._getXML(url);
//PL._onResponse(rsp); */
},
// Get object reference
_getObject: function(obj) {
if (typeof obj == "string") {
return document.getElementById(obj);
} else {
// pass through object reference
return obj;
}
},
_getWebRoot: function() {
/** Return directory of this relative to document URL. */
if (PL.webRoot != null) {
return PL.webRoot;
}
//derive the baseDir value by looking for the script tag that loaded this file
var head = document.getElementsByTagName('head')[0];
var nodes = head.childNodes;
for (var i = 0; i < nodes.length; ++i) {
var src = nodes.item(i).src;
if (src) {
var index = src.indexOf("ajax-pushlet-client.js");
if (index >= 0) {
index = src.indexOf("lib");
PL.webRoot = src.substring(0, index);
break;
}
}
}
return PL.webRoot;
},
// Get XML doc from server
// On response optional callback fun is called with optional user data.
_getXML: function(url, callback) {
// Obtain XMLHttpRequest object
var xmlhttp = new XMLHttpRequest();
if (!xmlhttp || xmlhttp == null) {
alert('No browser XMLHttpRequest (AJAX) support');
return;
}
// Setup optional async response handling via callback
var cb = callback;
var async = false;
if (cb) {
// Async mode
async = true;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
// Processing statements go here...
cb(xmlhttp.responseXML);
// Avoid memory leaks in IE
// 12.may.2007 thanks to Julio Santa Cruz
xmlhttp = null;
} else {
var event = new PushletEvent();
event.put('p_event', 'error')
event.put('p_reason', '[pushlet] problem retrieving XML data:\n' + xmlhttp.statusText);
PL._onEvent(event);
}
}
};
}
// Open URL
xmlhttp.open('GET', url, async);
// Send XML to KW server
xmlhttp.send(null);
if (!cb) {
if (xmlhttp.status != 200) {
var event = new PushletEvent();
event.put('p_event', 'error')
event.put('p_reason', '[pushlet] problem retrieving XML data:\n' + xmlhttp.statusText);
PL._onEvent(event)
return null;
}
// Sync mode (no callback)
// alert(xmlhttp.responseText);
return xmlhttp.responseXML;
}
},
_init: function () {
PL._showStatus();
PL._setStatus('initializing...');
/*
Setup Cross-Browser XMLHttpRequest v1.2
Emulate Gecko 'XMLHttpRequest()' functionality in IE and Opera. Opera requires
the Sun Java Runtime Environment <http://www.java.com/>.
by Andrew Gregory
http://www.scss.com.au/family/andrew/webdesign/xmlhttprequest/
This work is licensed under the Creative Commons Attribution License. To view a
copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
94305, USA.
*/
// IE support
if (window.ActiveXObject && !window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
var msxmls = new Array(
'Msxml2.XMLHTTP.5.0',
'Msxml2.XMLHTTP.4.0',
'Msxml2.XMLHTTP.3.0',
'Msxml2.XMLHTTP',
'Microsoft.XMLHTTP');
for (var i = 0; i < msxmls.length; i++) {
try {
return new ActiveXObject(msxmls[i]);
} catch (e) {
}
}
return null;
};
}
// ActiveXObject emulation
if (!window.ActiveXObject && window.XMLHttpRequest) {
window.ActiveXObject = function(type) {
switch (type.toLowerCase()) {
case 'microsoft.xmlhttp':
case 'msxml2.xmlhttp':
case 'msxml2.xmlhttp.3.0':
case 'msxml2.xmlhttp.4.0':
case 'msxml2.xmlhttp.5.0':
return new XMLHttpRequest();
}
return null;
};
}
PL.pushletURL = PL._getWebRoot() + 'pushlet.srv';
PL._setStatus('initialized');
PL.state = PL.STATE_READY;
},
/** Handle incoming events from server. */
_onEvent: function (event) {
// Create a PushletEvent object from the arguments passed in
// push.arguments is event data coming from the Server
PL.debug('_onEvent()', event.toString());
// Do action based on event type
var eventType = event.getEvent();
if (eventType == 'data') {
PL._setStatus('data');
PL._doCallback(event, window.onData);
} else if (eventType == 'refresh') {
if (PL.state < PL.STATE_LISTENING) {
PL._setStatus('not refreshing state=' + PL.STATE_LISTENING);
}
var timeout = event.get('p_wait');
setTimeout(function () {
PL._doRequest('refresh');
}, timeout);
return;
} else if (eventType == 'error') {
PL.state = PL.STATE_ERROR;
PL._setStatus('server error: ' + event.get('p_reason'));
PL._doCallback(event, window.onError);
} else if (eventType == 'join-ack') {
PL.state = PL.STATE_JOINED;
PL.sessionId = event.get('p_id');
PL._setStatus('connected');
PL._doCallback(event, window.onJoinAck);
} else if (eventType == 'join-listen-ack') {
PL.state = PL.STATE_LISTENING;
PL.sessionId = event.get('p_id');
PL._setStatus('join-listen-ack');
PL._doCallback(event, window.onJoinListenAck);
} else if (eventType == 'listen-ack') {
PL.state = PL.STATE_LISTENING;
PL._setStatus('listening');
PL._doCallback(event, window.onListenAck);
} else if (eventType == 'hb') {
PL._setStatus('heartbeat');
PL._doCallback(event, window.onHeartbeat);
} else if (eventType == 'hb-ack') {
PL._doCallback(event, window.onHeartbeatAck);
} else if (eventType == 'leave-ack') {
PL._setStatus('disconnected');
PL._doCallback(event, window.onLeaveAck);
} else if (eventType == 'refresh-ack') {
PL._doCallback(event, window.onRefreshAck);
} else if (eventType == 'subscribe-ack') {
PL._setStatus('subscribed to ' + event.get('p_subject'));
PL._doCallback(event, window.onSubscribeAck);
} else if (eventType == 'unsubscribe-ack') {
PL._setStatus('unsubscribed');
PL._doCallback(event, window.onUnsubscribeAck);
} else if (eventType == 'abort') {
PL.state = PL.STATE_ERROR;
PL._setStatus('abort');
PL._doCallback(event, window.onAbort);
} else if (eventType.match(/nack$/)) {
PL._setStatus('error response: ' + event.get('p_reason'));
PL._doCallback(event, window.onNack);
}
},
/** Handle XMLHttpRequest response XML. */
_onResponse: function(xml) {
PL.debug('_onResponse', xml);
var events = PL._rsp2Events(xml);
if (events == null) {
PL._setStatus('null events')
return;
}
delete xml;
PL.debug('_onResponse eventCnt=', events.length);
// Go through all <event/> elements
for (i = 0; i < events.length; i++) {
PL._onEvent(events[i]);
}
},
/** Convert XML response to PushletEvent objects. */
_rsp2Events: function(xml) {
// check empty response or xml document
if (!xml || !xml.documentElement) {
return null;
}
// Convert xml doc to array of PushletEvent objects
var eventElements = xml.documentElement.getElementsByTagName('event');
var events = new Array(eventElements.length);
for (i = 0; i < eventElements.length; i++) {
events[i] = new PushletEvent(eventElements[i]);
}
return events;
},
statusMsg: 'null',
statusChanged: false,
statusChar: '|',
_showStatus: function() {
// To show progress
if (PL.statusChanged == true) {
if (PL.statusChar == '|') PL.statusChar = '/';
else if (PL.statusChar == '/') PL.statusChar = '--';
else if (PL.statusChar == '--') PL.statusChar = '\\';
else PL.statusChar = '|';
PL.statusChanged = false;
}
window.defaultStatus = PL.statusMsg;
window.status = PL.statusMsg + ' ' + PL.statusChar;
timeout = window.setTimeout('PL._showStatus()', 400);
},
_setStatus: function(status) {
PL.statusMsg = "pushlet - " + status;
PL.statusChanged = true;
},
/*************** Debug utility *******************************/
timestamp: 0,
debugWindow: null,
messages: new Array(),
messagesIndex: 0,
debugOn: false,
/** Send debug messages to a (D)HTML window. */
debug: function(label, value) {
if (PL.debugOn == false) {
return;
}
var funcName = "none";
// Fetch JS function name if any
if (PL.debug.caller) {
funcName = PL.debug.caller.toString()
funcName = funcName.substring(9, funcName.indexOf(")") + 1)
}
// Create message
var msg = "-" + funcName + ": " + label + "=" + value
// Add optional timestamp
var now = new Date()
var elapsed = now - PL.timestamp
if (elapsed < 10000) {
msg += " (" + elapsed + " msec)"
}
PL.timestamp = now;
// Show.
if ((PL.debugWindow == null) || PL.debugWindow.closed) {
PL.debugWindow = window.open("", "p_debugWin", "toolbar=no,scrollbars=yes,resizable=yes,width=600,height=400");
}
// Add message to current list
PL.messages[PL.messagesIndex++] = msg
// Write doc header
PL.debugWindow.document.writeln('<html><head><title>Pushlet Debug Window</title></head><body bgcolor=#DDDDDD>');
// Write the messages
for (var i = 0; i < PL.messagesIndex; i++) {
PL.debugWindow.document.writeln('<pre>' + i + ': ' + PL.messages[i] + '</pre>');
}
// Write doc footer and close
PL.debugWindow.document.writeln('</body></html>');
PL.debugWindow.document.close();
PL.debugWindow.focus();
}
}
/* Represents nl.justobjects.pushlet.Event in JS. */
function PushletEvent(xml) {
// Member variable setup; the assoc array stores the N/V pairs
this.arr = new Array();
this.getSubject = function() {
return this.get('p_subject');
}
this.getEvent = function() {
return this.get('p_event');
}
this.put = function(name, value) {
return this.arr[name] = value;
}
this.get = function(name) {
return this.arr[name];
}
this.toString = function() {
var res = '';
for (var i in this.arr) {
res = res + i + '=' + this.arr[i] + '\n';
}
return res;
}
this.toTable = function() {
var res = '<table border="1" cellpadding="3">';
var styleDiv = '<div style="color:black; font-family:monospace; font-size:10pt; white-space:pre;">'
for (var i in this.arr) {
res = res + '<tr><td bgColor=white>' + styleDiv + i + '</div></td><td bgColor=white>' + styleDiv + this.arr[i] + '</div></td></tr>';
}
res += '</table>'
return res;
}
// Optional XML element <event name="value" ... />
if (xml) {
// Put the attributes in Map
for (var i = 0; i < xml.attributes.length; i++) {
this.put(xml.attributes[i].name, xml.attributes[i].value);
}
}
}
/**********************************************************************
START - OLD application functions (LEFT HERE FOR FORWARD COMPAT)
***********************************************************************/
// Debug util
function p_debug(aBool, aLabel, aMsg) {
if (aBool == false) {
return;
}
PL.setDebug(true);
PL.debug(aLabel, aMsg);
PL.setDebug(false);
}
// Embed pushlet frame in page (OBSOLETE)
function p_embed(thePushletWebRoot) {
alert('Pushlet: p_embed() is no longer required for AJAX client')
}
// Join the pushlet server
function p_join() {
PL.join();
}
// Create data event channel with the server
function p_listen(aSubject, aMode) {
// Note: mode is fixed to 'pull'
PL.listen(aSubject);
}
// Shorthand: Join the pushlet server and start listening immediately
function p_join_listen(aSubject) {
PL.joinListen(aSubject);
}
// Leave the pushlet server
function p_leave() {
PL.leave();
}
// Send heartbeat event; callback is onHeartbeatAck()
function p_heartbeat() {
PL.heartbeat();
}
// Publish to a subject
function p_publish(aSubject, nvPairs) {
var args = p_publish.arguments;
// Put the arguments' name/value pairs in the URI
var query = '';
var amp = '';
for (var i = 1; i < args.length; i++) {
if (i > 1) {
amp = '&';
}
query = query + amp + args[i] + '=' + args[++i];
}
PL.publish(aSubject, query);
}
// Subscribe to a subject with optional label
function p_subscribe(aSubject, aLabel) {
PL.subscribe(aSubject, aLabel);
}
// Unsubscribe from a subject
function p_unsubscribe(aSid) {
PL.unsubscribe(aSid);
}
/**********************************************************************
END - Public application functions (LEFT HERE FOR FORWARD COMPAT)
***********************************************************************/
// Initialize when page completely loaded
PL._addEvent(window, 'load', PL._init, false);