comet4j

作者: 天涯笑笑生 | 来源:发表于2017-11-20 16:34 被阅读53次

初步学习可以看看
java web 服务器推送技术--comet4j
Comet HTTP服务器推送框架之Comet4J
java 使用comet4j向客户端主动推送例子

比较完善的应用
comet4j运用-- 聊天室例子
comet4j运用-- 间隔时间推送服务器剩余内存大小

几篇不错的文章
使用 Java 实现 Comet 风格的 Web 应用
浅析Comet技术在Java Web实时系统开发中的应用
java web 服务器推送技术 comet实现(原理)

转载自comet4j,开发文档被墙了,就直接贴过来方便查看
Tomcat 8 测试失败,不知啥时更新(有成功的望)
注:和Tomcat7 相同的配置,服务器端不报错,但是客户端会报“JSON 解析错误”,404的错误

简介

Comet4J是一个微型的即时推送框架,它分为服务端与客户端两部分,你只要将服务器端(JAR文件,目前仅支持Tomcat6、7)放入WEB-INF\lib,客户端(JavaScript文件)引入到页面,那么你的应用就具备了向客户端推送信息的能力,而你仅需要在服务器端调用Comet4J所提供发送方法,信息就会被主动的推送到客户的浏览器上。

准备工作

  • 下载服务端jar文件

Comet4J目前仅支持Tomcat6、7版本,根据您所使用的Tomcat版本下载【comet4j-tomcat6.jar】或【comet4j-tomcat7.jar】文件放置到WEB项目的WEB-INF\lib目录下。

  • 下载客户端js文件

    下载【comet4j.js】到您的项目中,比如:WebContent\js目录下。

  • 修改服务器配置文件

    因为Comet4J工作在NIO方式下,所以我们需要调整服务器连接器配置,更换为NOI连接器。 打开server.xml文件将找到原先的连接器配置:
    <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    替换为:
    <Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

  • 在web.xml中加载Comet4J框架

    最后我们需要在web.xml配置侦听和comet连接地址,以使Comet4J生效:

 <listener> 
     <description>Comet4J容器侦听</description> 
     <listener-class>org.comet4j.core.CometAppListener</listener-class> 
</listener> 

<servlet> 
    <description>Comet连接[默认:org.comet4j.core.CometServlet]</description>
    <display-name>CometServlet</display-name> 
    <servlet-name>CometServlet</servlet-name>
    <servlet-class>org.comet4j.core.CometServlet</servlet-class> </servlet>

 <servlet-mapping> 
    <servlet-name>CometServlet</servlet-name>
    <url-pattern>/conn</url-pattern>
 </servlet-mapping>

注: 1和2 的位置反过来就会报错

客户端使用简介

客户端是一个JavaScript文件(comet4j-0.0.2.js),其中最重要的是JS.Connector和JS.Engine两个类。JS.Connector负责与服务器建立并保持连接,而JS.Engine类负责将服务器推送过来的消息转化为开发人员可以处理的消息事件,并分发出去,关于客户端的API请参见:http://doc.comet4j.tk/jsdocs/ 。大多数情况下,我们仅需要使用JS.Engine类就可以完成多数的开发工作。

JS.Engine类是一个静态类,在一个页面中只有一个JS.Engine类的实例。它除了负责把服务器推过来的消息转化为事件分发以外,与服务器的连接与断开也由此类负责。

JS.Engine.start方法

JS.Engine.start(String str)和JS.Engine.stop(String str)分别控制连接和断开动作,start方法需要传入一个字符串参数,用来指定您配置的Comet4J连接地址。比如按前面准备工作的配置了CometServlet的地址为/conn,那么可以这样写: JS.Engine.start('/conn'); 上段代码我们让浏览器与服务器进行连接,当连接成功以后JS.Engine类会发出"start"事件,如何进行事件的处理我们稍后介绍。

JS.Engine.stop方法

我们也能够让连接断开: JS.Engine.stop('主动断开'); 上面代码我们让连接断开,并传入了一个“主动断开”这样一个断开的原因。如果您并不需要对断开的原因进行说明,也可以不传递参数: JS.Engine.stop();

JS.Engine类的事件处理

上面我们介绍了如何使用start和stop方法来建立和断开连接,当成功建立连接已后JS.Engine会发出"start"事件,当断开后会发出“stop”事件,当收到某个通道推送过来的信息时也会发出与通道标识同名的事件。您可以事先在中使用JS.Engine.on方法来注册事件处理函数。例如:

JS.Engine.on('start',function(cId, channelList, engine)
     { 
           alert('连接已建立,连接ID为:' + cId);
    }
); 

JS.Engine.on('stop',function(cause, cId, url, engine)
     {  
          alert('连接已断开,连接ID为:' + cId + ',断开原因:' + cause + ',断开的连接地址:'+ url);
    }
); 

也可以将上段代码写成,下面代码与上段代码完全等效:

 JS.Engine.on(
{
       start : function(cId, channelList, engine)
       {
            alert('连接已建立,连接ID为:' + cId); 
      },     
     stop : function(cause, cId, url, engine)
     { 
            alert('连接已断开,连接ID为:' + cId + ',断开原因:' + cause + ',断开的连接地址:'+ url);
      } 
}); 

接下来,介绍一下如何对服务器推送过来的消息进行处理。在介绍之前,我们假设后台已经注册了一个"hello"的应用通道标识,并且只向客户端推送简单的字符串信息。先看如下代码:

JS.Engine.on('hello',function(text){ alert(text); }); 

这样当服务器端使用"hello"通道标识推送过来的消息就可以由上段代码进行处理,将推送过来的信息弹出。

特别注意:以上代码在事件处理函数中使用了alert仅为说明函数功能,实际使用中,在事件处理函数中切勿使用alert、prompt、confirm等可以中断脚本运行的函数,因为Engine需要实时的保持工作状态。

服务器端使用简介

服务端由一个Jar包组成,其中最重的是CometContext和CometEngine两个类。

Comet Context 类

CometContext是一个单态类,通过其getInstance方法来获得实例,它主要负责框架的一些初始化工作保存着一些参数的配置值,除此之外它还有一个更重要的职责——负责注册应用通道标识。如果您想使用框架来实现自己的应用,那么您必需要为自己的应用分配一个唯一的通道标识,并将此通道标识在WEB容器启动时使用CometContext的registChannel方法进行注册,这样,客户端才可以正确接受此应用所推送的消息。注册一个通道标识非常简单:

CometContext.getInstance().registChannel("hello"); 

这样便注册了一个标识为“hello”的应用通道,而客户也可以通过JS.Engine.on('hello',function(msg){...})的形式来接收并处理来自此通道的消息。

Comet Engine 类

另一个重要的类是CometEngine,它除了负责对连接的处理之外,对于开发人员而言,更加常用的可能是它所提供的sendTo或sendToAll方法来向客户端发送消息:

 String channel = "hello"; String someConnectionId = "1125-6634-888";
 engine.sendToAll(channel , "我来了!"); 
engine.sendTo(channel , engine.getConnection(someConnectionId),“Hi,我是XXX”); 

上面代码使用sendToAll方法向所有客户端在"hello"通道上发送了“我来了!”这样一条消息,然后又使用sendTo在同样的通道上向某一个连接发送了“Hi,我是XXX”消息。 CometEngine另外一个很重要的地方在于,它是框架工作的事件引擎的集散地,它提供了BeforeConnectEvent、BeforeDropEvent、ConnectEvent、DropEvent、MessageEvent等事件。通过对这些事件的处理来实现具体的功能:

class JoinListener extends ConnectListener { 
    @Override 
     public boolean handleEvent(ConnectEvent anEvent) {                              
                CometConnection conn = anEvent.getConn();       
                  CometContext.getInstance().getEngine().sendTo("hello",   conn.getId(),"欢迎上线");
  } 
}


CometEngine engine = CometContext.getInstance().getEngine(); 
engine.addConnectListener(new JoinListener());

上面先定义了一个JoinListener并实现了父类ConnectListener的handleEvent抽像方法,然后使用engine.addConnectListener来注册这个事件侦听。这样,在有客户与服务器成功建立连接已后,就可以向此客户端推送一条欢迎信息。
在线Demo体验

http://www.comet4j.org:8080/comet4j/


comet4j.js 源码

有兴趣可以研究下,不是很多

/*
 * Comet4J JavaScript Client V0.1.0
 * Copyright(c) 2011, jinghai.xiao@gamil.com.
 * http://code.google.com/p/comet4j/
 * This code is licensed under BSD license. Use it as you wish, 
 * but keep this copyright intact.
 */
var JS = {
  version: '0.0.2'
};
JS.Runtime = (function () {
  var ua = navigator.userAgent.toLowerCase(),
  check = function (r) {
    return r.test(ua);
  },
  isOpera = check(/opera/),
  isFirefox = check(/firefox/),
  isChrome = check(/chrome/),
  isWebKit = check(/webkit/),
  isSafari = !isChrome && check(/safari/),
  isSafari2 = isSafari && check(/applewebkit\/4/),
  isSafari3 = isSafari && check(/version\/3/),
  isSafari4 = isSafari && check(/version\/4/),
  isIE = !isOpera && check(/msie/),
  isIE7 = isIE && check(/msie 7/),
  isIE8 = isIE && check(/msie 8/),
  isIE6 = isIE && !isIE7 && !isIE8,
  isGecko = !isWebKit && check(/gecko/),
  isGecko2 = isGecko && check(/rv:1\.8/),
  isGecko3 = isGecko && check(/rv:1\.9/),
  isWindows = check(/windows|win32/),
  isMac = check(/macintosh|mac os x/),
  isAir = check(/adobeair/),
  isLinux = check(/linux/);
  return {
    isOpera: isOpera,
    isFirefox: isFirefox,
    isChrome: isChrome,
    isWebKit: isWebKit,
    isSafari: isSafari,
    isSafari2: isSafari2,
    isSafari3: isSafari3,
    isSafari4: isSafari4,
    isIE: isIE,
    isIE7: isIE7,
    isIE8: isIE8,
    isIE6: isIE6,
    isGecko: isGecko,
    isGecko2: isGecko2,
    isGecko3: isGecko3,
    isWindows: isWindows,
    isMac: isMac,
    isAir: isAir,
    isLinux: isLinux
  };
}());
JS.isOpera = JS.Runtime.isOpera;
JS.isFirefox = JS.Runtime.isFirefox;
JS.isChrome = JS.Runtime.isChrome;
JS.isWebKit = JS.Runtime.isWebKit;
JS.isSafari = JS.Runtime.isSafari;
JS.isSafari2 = JS.Runtime.isSafari2;
JS.isSafari3 = JS.Runtime.isSafari3;
JS.isSafari4 = JS.Runtime.isSafari4;
JS.isIE = JS.Runtime.isIE;
JS.isIE7 = JS.Runtime.isIE7;
JS.isIE8 = JS.Runtime.isIE8;
JS.isIE6 = JS.Runtime.isIE6;
JS.isGecko = JS.Runtime.isGecko;
JS.isGecko2 = JS.Runtime.isGecko2;
JS.isGecko3 = JS.Runtime.isGecko3;
JS.isWindows = JS.Runtime.isWindows;
JS.isMac = JS.Runtime.isMac;
JS.isAir = JS.Runtime.isAir;
JS.isLinux = JS.Runtime.isLinux;
JS.Syntax = {
  nameSpace: function () {
    if (arguments.length) {
      var o,
      d,
      v;
      for (var i = 0, len = arguments.length; i < len; i++) {
        v = arguments[i];
        if (!v) {
          continue;
        }
        d = v.split('.');
        for (var j = 0, len = d.length; j < len; j++) {
          if (!d[j]) {
            continue;
          }
          o = window[d[j]] = window[d[j]] || {
          };
        }
      }
    }
    return o;
  },
  apply: function (o, c, defaults) {
    if (defaults) {
      JS.Syntax.apply(o, defaults);
    }
    if (o && c && typeof c == 'object') {
      for (var p in c) {
        o[p] = c[p];
      }
    }
    return o;
  },
  override: function (origclass, overrides) {
    if (overrides) {
      var p = origclass.prototype;
      JS.Syntax.apply(p, overrides);
      if (JS.Runtime.isIE && overrides.hasOwnProperty('toString')) {
        p.toString = overrides.toString;
      }
    }
  },
  extend: function () {
    var io = function (o) {
      for (var m in o) {
        this[m] = o[m];
      }
    };
    var oc = Object.prototype.constructor;
    return function (sb, sp, overrides) {
      if (JS.Syntax.isObject(sp)) {
        overrides = sp;
        sp = sb;
        sb = overrides.constructor != oc ? overrides.constructor : function () {
          sp.apply(this, arguments);
        };
      }
      var F = function () {
      },
      sbp,
      spp = sp.prototype;
      F.prototype = spp;
      sbp = sb.prototype = new F();
      sbp.constructor = sb;
      sb.superclass = spp;
      if (spp.constructor == oc) {
        spp.constructor = sp;
      }
      sb.override = function (o) {
        JS.Syntax.override(sb, o);
      };
      sbp.superclass = sbp.supr = (function () {
        return spp;
      });
      sbp.override = io;
      JS.Syntax.override(sb, overrides);
      sb.extend = function (o) {
        return JS.Syntax.extend(sb, o);
      };
      return sb;
    };
  }(),
  callBack: function (fn, scope, arg) {
    if (JS.isFunction(fn)) {
      return fn.apply(scope || window, arg || [
      ]);
    }
  },
  isEmpty: function (v, allowBlank) {
    return v === null || v === undefined || ((Ext.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);
  },
  isArray: function (v) {
    return Object.prototype.toString.apply(v) === '[object Array]';
  },
  isDate: function (v) {
    return Object.prototype.toString.apply(v) === '[object Date]';
  },
  isObject: function (v) {
    return !!v && Object.prototype.toString.call(v) === '[object Object]';
  },
  isPrimitive: function (v) {
    return Ext.isString(v) || Ext.isNumber(v) || Ext.isBoolean(v);
  },
  isFunction: function (v) {
    return Object.prototype.toString.apply(v) === '[object Function]';
  },
  isNumber: function (v) {
    return typeof v === 'number' && isFinite(v);
  },
  isString: function (v) {
    return typeof v === 'string';
  },
  isBoolean: function (v) {
    return typeof v === 'boolean';
  },
  isElement: function (v) {
    return !!v && v.tagName;
  },
  isDefined: function (v) {
    return typeof v !== 'undefined';
  },
  toArray: function () {
    return JS.isIE ? function (a, i, j, res) {
      res = [
      ];
      for (var x = 0, len = a.length; x < len; x++) {
        res.push(a[x]);
      }
      return res.slice(i || 0, j || res.length);
    }
     : function (a, i, j) {
      return Array.prototype.slice.call(a, i || 0, j || a.length);
    };
  }()
};
JS.ns = JS.Syntax.nameSpace;
JS.apply = JS.Syntax.apply;
JS.override = JS.Syntax.override;
JS.extend = JS.Syntax.extend;
JS.callBack = JS.Syntax.callBack;
JS.isEmpty = JS.Syntax.isEmpty;
JS.isArray = JS.Syntax.isArray;
JS.isDate = JS.Syntax.isDate;
JS.isObject = JS.Syntax.isObject;
JS.isPrimitive = JS.Syntax.isPrimitive;
JS.isFunction = JS.Syntax.isFunction;
JS.isNumber = JS.Syntax.isNumber;
JS.isString = JS.Syntax.isString;
JS.isBoolean = JS.Syntax.isBoolean;
JS.isElement = JS.Syntax.isElement;
JS.isDefined = JS.Syntax.isDefined;
JS.toArray = JS.Syntax.toArray;
JS.DomEvent = {
  on: function (el, name, fun, scope) {
    if (el.addEventListener) {
      el.addEventListener(name, function () {
        JS.callBack(fun, scope, arguments);
      }, false);
    } else {
      el.attachEvent('on' + name, function () {
        JS.callBack(fun, scope, arguments);
      });
    }
  },
  un: function (el, name, fun, scope) {
    if (el.removeEventListener) {
      el.removeEventListener(name, fun, false);
    } else {
      el.detachEvent('on' + name, fun);
    }
  },
  stop: function (e) {
    e.returnValue = false;
    if (e.preventDefault) {
      e.preventDefault();
    }
    JS.DomEvent.stopPropagation(e);
  },
  stopPropagation: function (e) {
    e.cancelBubble = true;
    if (e.stopPropagation) {
      e.stopPropagation();
    }
  }
};
JS.on = JS.DomEvent.on;
JS.un = JS.DomEvent.un;
JS.DelayedTask = function (fn, scope, args) {
  var me = this,
  id,
  call = function () {
    clearInterval(id);
    id = null;
    fn.apply(scope, args || [
    ]);
  };
  me.delay = function (delay, newFn, newScope, newArgs) {
    me.cancel();
    fn = newFn || fn;
    scope = newScope || scope;
    args = newArgs || args;
    id = setInterval(call, delay);
  };
  me.cancel = function () {
    if (id) {
      clearInterval(id);
      id = null;
    }
  };
};
JS.ns('JS.Observable');
JS.Observable = function (o) {
  JS.apply(this, o || JS.toArray(arguments) [0]);
  if (this.events) {
    this.addEvents(this.events);
  }
  if (this.listeners) {
    this.on(this.listeners);
    delete this.listeners;
  }
};
JS.Observable.prototype = {
  on: function (eventName, fn, scope, o) {
    if (JS.isString(eventName)) {
      this.addListener(eventName, fn, scope, o);
    } else if (JS.isObject(eventName)) {
      this.addListeners(eventName, scope, o);
    }
  },
  fireEvent: function () {
    var arg = JS.toArray(arguments),
    eventName = arg[0].toLowerCase(),
    e = this.events[eventName];
    if (e && !JS.isBoolean(e)) {
      return e.fire.apply(e, arg.slice(1));
    }
  },
  addEvent: function (eventName) {
    if (!JS.isObject(this.events)) {
      this.events = {
      };
    }
    if (this.events[eventName]) {
      return;
    }
    if (JS.isString(eventName)) {
      this.events[eventName.toLowerCase()] = true;
    } else if (eventName instanceof JS.Event) {
      this.events[eventName.name.toLowerCase()] = eventName;
    }
  },
  addEvents: function (arr) {
    if (JS.isArray(arr)) {
      for (var i = 0, len = arr.length; i < len; i++) {
        this.addEvent(arr[i]);
      }
    }
  },
  addListener: function (eventName, fn, scope, o) {
    eventName = eventName.toLowerCase();
    var e = this.events[eventName];
    if (e) {
      if (JS.isBoolean(e)) {
        e = this.events[eventName] = new JS.Event(eventName, this);
      }
      e.addListener(fn, scope, o);
    }
  },
  addListeners: function (obj, scope, o) {
    if (JS.isObject(obj)) {
      for (var p in obj) {
        this.addListener(p, obj[p], scope, o);
      }
    }
  },
  removeListener: function (eventName, fn, scope) {
    eventName = eventName.toLowerCase();
    var e = this.events[eventName];
    if (e && !JS.isBoolean(e)) {
      e.removeListener(fn, scope);
    }
  },
  clearListeners: function () {
    var events = this.events,
    e;
    for (var p in events) {
      e = events[p];
      if (!JS.isBoolean(e)) {
        e.clearListeners();
      }
    }
  },
  clearEvents: function () {
    var events = this.events;
    this.clearListeners();
    for (var p in events) {
      this.removeEvent(p);
    }
  },
  removeEvent: function (eventName) {
    var events = this.events,
    e;
    if (events[eventName]) {
      e = events[eventName];
      if (!JS.isBoolean(e)) {
        e.clearListeners();
      }
      delete events[eventName];
    }
  },
  removeEvents: function (eventName) {
    if (JS.isString(eventName)) {
      this.removeEvent(eventName);
    } else if (JS.isArray(eventName) && eventName.length > 0) {
      for (var i = 0, len = eventName.length; i < len; i++) {
        this.removeEvent(eventName[i]);
      }
    }
  },
  hasEvent: function (eventName) {
    return this.events[eventName] ? true : false;
  },
  hasListener: function (eventName, fn, scope) {
    var events = this.events,
    e = events[eventName];
    if (!JS.isBoolean(e)) {
      return e.hasListener(fn, scope);
    }
    return false;
  },
  suspendEvents: function () {
  },
  resumeEvents: function () {
  }
};
JS.Event = function (name, caller) {
  this.name = name.toLowerCase();
  this.caller = caller;
  this.listeners = [
  ];
};
JS.Event.prototype = {
  fire: function () {
    var
    listeners = this.listeners,
    i = listeners.length - 1;
    for (; i > - 1; i--) {
      if (listeners[i].execute.apply(listeners[i], arguments) === false) {
        return false;
      }
    }
    return true;
  },
  addListener: function (fn, scope, o) {
    scope = scope || this.caller;
    if (this.hasListener(fn, scope) == - 1) {
      this.listeners.push(new JS.Listener(fn, scope, o));
    }
  },
  removeListener: function (fn, scope) {
    var index = this.hasListener(fn, scope);
    alert(index);
    if (index != - 1) {
      this.listeners.splice(index, 1);
    }
  },
  hasListener: function (fn, scope) {
    var i = 0,
    listeners = this.listeners,
    len = listeners.length;
    for (; i < len; i++) {
      if (listeners[i].equal(fn, scope)) {
        return i;
      }
    }
    return - 1;
  },
  clearListeners: function () {
    var i = 0,
    listeners = this.listeners,
    len = listeners.length;
    for (; i < len; i++) {
      listeners[i].clear();
    }
    this.listeners.splice(0);
  }
};
JS.Listener = function (fn, scope, o) {
  this.handler = fn;
  this.scope = scope;
  this.o = o;
};
JS.Listener.prototype = {
  execute: function () {
    return JS.callBack(this.handler, this.scope, arguments);
  },
  equal: function (fn, scope) {
    return this.handler === fn ? true : false;
  },
  clear: function () {
    delete this.handler;
    delete this.scope;
    delete this.o;
  }
};
JS.ns('JS.HTTPStatus', 'JS.XMLHttpRequest');
JS.HTTPStatus = {
  '100': 'Continue',
  '101': 'Switching Protocols',
  '200': 'OK',
  '201': 'Created',
  '202': 'Accepted',
  '203': 'Non-Authoritative Information',
  '204': 'No Content',
  '205': 'Reset Content',
  '206': 'Partial Content',
  '300': 'Multiple Choices',
  '301': 'Moved Permanently',
  '302': 'Found',
  '303': 'See Other',
  '304': 'Not Modified',
  '305': 'Use Proxy',
  '306': 'Unused',
  '307': 'Temporary Redirect',
  '400': 'Bad Request',
  '401': 'Unauthorized',
  '402': 'Payment Required',
  '403': 'Forbidden',
  '404': 'Not Found',
  '405': 'Method Not Allowed',
  '406': 'Not Acceptable',
  '407': 'Proxy Authentication Required',
  '408': 'Request Timeout',
  '409': 'Conflict',
  '410': 'Gone',
  '411': 'Length Required',
  '412': 'Precondition Failed',
  '413': 'Request Entity Too Large',
  '414': 'Request-URI Too Long',
  '415': 'Unsupported Media Type',
  '416': 'Requested Range Not Satisfiable',
  '417': 'Expectation Failed',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway',
  '503': 'Service Unavailable',
  '504': 'Gateway Timeout',
  '505': 'HTTP Version Not Supported'
};
JS.HTTPStatus.OK = 200;
JS.HTTPStatus.BADREQUEST = 400;
JS.HTTPStatus.FORBIDDEN = 403;
JS.HTTPStatus.NOTFOUND = 404;
JS.HTTPStatus.TIMEOUT = 408;
JS.HTTPStatus.SERVERERROR = 500;
JS.XMLHttpRequest = JS.extend(JS.Observable, {
  enableCache: false,
  timeout: 0,
  isAbort: false,
  specialXHR: '',
  _xhr: null,
  readyState: 0,
  status: 0,
  statusText: '',
  responseText: '',
  responseXML: null,
  constructor: function () {
    var self = this;
    this.addEvents(['readyStateChange',
    'timeout',
    'abort',
    'error',
    'load',
    'progress']);
    JS.XMLHttpRequest.superclass.constructor.apply(this, arguments);
    this._xhr = this.createXmlHttpRequestObject();
    this._xhr.onreadystatechange = function () {
      self.doReadyStateChange();
    };
  },
  timeoutTask: null,
  delayTimeout: function () {
    if (this.timeout) {
      if (!this.timeoutTask) {
        this.timeoutTask = new JS.DelayedTask(function () {
          if (this._xhr.readyState != 4) {
            this.fireEvent('timeout', this, this._xhr);
          } else {
            this.cancelTimeout();
          }
        }, this);
      }
      this.timeoutTask.delay(this.timeout);
    }
  },
  cancelTimeout: function () {
    if (this.timeoutTask) {
      this.timeoutTask.cancel();
    }
  },
  createXmlHttpRequestObject: function () {
    var activeX = [
      'Msxml2.XMLHTTP.6.0',
      'Msxml2.XMLHTTP.5.0',
      'Msxml2.XMLHTTP.4.0',
      'Msxml2.XMLHTTP.3.0',
      'Msxml2.XMLHTTP',
      'Microsoft.XMLHTTP'
    ],
    xhr,
    specialXHR = this.specialXHR;
    if (specialXHR) {
      if (JS.isString(specialXHR)) {
        return new ActiveXObject(specialXHR);
      } else {
        return specialXHR;
      }
    }
    try {
      xhr = new XMLHttpRequest();
    } catch (e) {
      for (var i = 0; i < activeX.length; ++i) {
        try {
          xhr = new ActiveXObject(activeX[i]);
          break;
        } catch (e) {
        }
      }
    } finally {
      return xhr;
    }
  },
  doReadyStateChange: function () {
    this.delayTimeout();
    var xhr = this._xhr;
    try {
      this.readyState = xhr.readyState;
    } catch (e) {
      this.readyState = 0;
    }
    try {
      this.status = xhr.status;
    } catch (e) {
      this.status = 0;
    }
    try {
      this.statusText = xhr.statusText;
    } catch (e) {
      this.statusText = '';
    }
    try {
      this.responseText = xhr.responseText;
    } catch (e) {
      this.responseText = '';
    }
    try {
      this.responseXML = xhr.responseXML;
    } catch (e) {
      this.responseXML = null;
    }
    this.fireEvent('readyStateChange', this.readyState, this.status, this, xhr);
    if (this.readyState == 3 && (this.status >= 200 && this.status < 300)) {
      this.fireEvent('progress', this, xhr);
    }
    if (this.readyState == 4) {
      this.cancelTimeout();
      var status = this.status;
      if (status == 0) {
        this.fireEvent('error', this, xhr);
      } else if (status >= 200 && status < 300) {
        this.fireEvent('load', this, xhr);
      } else if (status >= 400 && status != 408) {
        this.fireEvent('error', this, xhr);
      } else if (status == 408) {
        this.fireEvent('timeout', this, xhr);
      }
    }
    this.onreadystatechange();
  },
  onreadystatechange: function () {
  },
  open: function (method, url, async, username, password) {
    if (!url) {
      return;
    }
    if (!this.enableCache) {
      if (url.indexOf('?') != - 1) {
        url += '&ram=' + Math.random();
      } else {
        url += '?ram=' + Math.random();
      }
    }
    this._xhr.open(method, url, async, username, password);
  },
  send: function (content) {
    this.delayTimeout();
    this.isAbort = false;
    this._xhr.send(content);
  },
  abort: function () {
    this.isAbort = true;
    this.cancelTimeout();
    this._xhr.abort();
    if (JS.isIE) {
      var self = this;
      self._xhr.onreadystatechange = function () {
        self.doReadyStateChange();
      };
    }
    this.fireEvent('abort', this, this._xhr);
  },
  setRequestHeader: function (header, value) {
    this._xhr.setRequestHeader(header, value);
  },
  getResponseHeader: function (header) {
    return this._xhr.getResponseHeader(header);
  },
  getAllResponseHeaders: function () {
    return this._xhr.getAllResponseHeaders();
  },
  setTimeout: function (t) {
    this.timeout = t;
  }
});
JS.ns('JS.AJAX');
JS.AJAX = (function () {
  var xhr = new JS.XMLHttpRequest();
  return {
    dataFormatError: '服务器返回的数据格式有误',
    urlError: '未指定url',
    post: function (url, param, callback, scope, asyn) {
      if (typeof url !== 'string') {
        throw new Error(this.urlError);
      }
      var asynchronous = true;
      if (asyn === false) {
        asynchronous = false;
      }
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && asynchronous) {
          JS.callBack(callback, scope, [
            xhr
          ]);
        }
      };
      xhr.open('POST', url, asynchronous);
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF8');
      xhr.send(param || null);
      if (!asynchronous) {
        JS.callBack(callback, scope, [
          xhr
        ]);
      }
    },
    get: function (url, param, callback, scope, asyn) {
      if (typeof url !== 'string') {
        throw new Error(this.urlError);
      }
      var asynchronous = true;
      if (asyn === false) {
        asynchronous = false;
      }
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && asynchronous) {
          JS.callBack(callback, scope, [
            xhr
          ]);
        }
      };
      xhr.open('GET', url, asynchronous);
      xhr.setRequestHeader('Content-Type', 'html/text;charset=UTF8');
      xhr.send(param || null);
      if (!asynchronous) {
        JS.callBack(callback, scope, [
          xhr
        ]);
      }
    },
    getText: function (url, jsonData, callback, scope, asyn) {
      this.get(url, jsonData, function (xhr) {
        if (scope) {
          callback.call(scope, xhr.responseText);
        } else {
          callback(xhr.responseText);
        }
      }, this, asyn);
    },
    getJson: function (url, jsonData, callback, scope, asyn) {
      this.get(url, jsonData, function (xhr) {
        var json = null;
        try {
          json = eval('(' + xhr.responseText + ')');
        } catch (e) {
          throw new Error(this.dataFormatError);
        }
        JS.callBack(callback, scope, [
          json
        ]);
      }, this, asyn);
    }
  };
}) ();
JS.ns('JS.Connector');
JS.Connector = JS.extend(JS.Observable, {
  version: '0.0.2',
  SYSCHANNEL: 'c4j',
  LLOOPSTYLE: 'lpool',
  STREAMSTYLE: 'stream',
  CMDTAG: 'cmd',
  url: '',
  param: '',
  revivalDelay: 100,
  cId: '',
  channels: [
  ],
  workStyle: '',
  emptyUrlError: 'URL为空',
  runningError: '连接正在运行',
  dataFormatError: '数据格式有误',
  running: false,
  _xhr: null,
  lastReceiveMessage: '',
  constructor: function () {
    JS.Connector.superclass.constructor.apply(this, arguments);
    this.addEvents(['beforeConnect',
    'connect',
    'beforeStop',
    'stop',
    'message',
    'revival']);
    if (JS.isIE7) {
      this._xhr = new JS.XMLHttpRequest({
        specialXHR: 'Msxml2.XMLHTTP.6.0'
      });
    } else {
      this._xhr = new JS.XMLHttpRequest();
    }
    this._xhr.addListener('progress', this.doOnProgress, this);
    this._xhr.addListener('load', this.doOnLoad, this);
    this._xhr.addListener('error', this.doOnError, this);
    this._xhr.addListener('timeout', this.revivalConnect, this);
    this.addListener('beforeStop', this.doDrop, this);
    JS.on(window, 'beforeunload', this.doDrop, this);
  },
  doDrop: function (url, cId, conn, xhr) {
    if (!this.running || !this.cId) {
      return;
    }
    try {
      var xhr = new JS.XMLHttpRequest();
      var url = this.url + '?' + this.CMDTAG + '=drop&cid=' + this.cId;
      xhr.open('GET', url, false);
      xhr.send(null);
      xhr = null;
    } catch (e) {
    };
  },
  dispatchServerEvent: function (msg) {
    this.fireEvent('message', msg.channel, msg.data, msg.time, this);
  },
  translateStreamData: function (responseText) {
    var str = responseText;
    if (this.lastReceiveMessage && str) {
      str = str.split(this.lastReceiveMessage);
      str = str.length ? str[str.length - 1] : '';
    }
    this.lastReceiveMessage = responseText;
    return str;
  },
  decodeMessage: function (msg) {
    var json = null;
    if (JS.isString(msg) && msg != '') {
      if (msg.charAt(0) == '<') {
        msg = msg.substring(1, msg.length);
      }
      if (msg.charAt(msg.length - 1) == '>') {
        msg = msg.substring(0, msg.length - 1);
      }
      msg = decodeURIComponent(msg);
      try {
        json = eval('(' + msg + ')');
      } catch (e) {
        this.stop('JSON转换异常');
        try {
          console.log('JSON转换异常:' + msg);
        } catch (e) {
        };
      }
    }
    return json;
  },
  doOnProgress: function (xhr) {
    if (this.workStyle === this.STREAMSTYLE) {
      var str = this.translateStreamData(xhr.responseText);
      var msglist = str.split('>');
      if (msglist.length > 0) {
        for (var i = 0, len = msglist.length; i < len; i++) {
          var json = this.decodeMessage(msglist[i]);
          if (json) {
            this.dispatchServerEvent(json);
          }
        }
      }
    }
  },
  doOnError: function (xhr) {
    this.stop('服务器异常');
  },
  doOnLoad: function (xhr) {
    if (this.workStyle === this.LLOOPSTYLE) {
      var json = this.decodeMessage(xhr.responseText);
      if (json) {
        this.dispatchServerEvent(json);
      }
    }
    this.revivalConnect();
  },
  startConnect: function () {
    if (this.running) {
      var url = this.url + '?' + this.CMDTAG + '=conn&cv=' + this.version + this.param;
      JS.AJAX.get(url, '', function (xhr) {
        var msg = this.decodeMessage(xhr.responseText);
        if (!msg) {
          this.stop('连接失败');
          return;
        }
        var data = msg.data;
        this.cId = data.cId;
        this.channels = data.channels;
        this.workStyle = data.ws;
        this._xhr.setTimeout(data.timeout * 2);
        this.fireEvent('connect', data.cId, data.channels, data.ws, data.timeout, this);
        this.revivalConnect();
      }, this);
    }
  },
  revivalConnect: function () {
    var self = this;
    if (this.running) {
      setTimeout(revival, this.revivalDelay);
    }
    function revival() {
      var xhr = self._xhr;
      var url = self.url + '?' + self.CMDTAG + '=revival&cid=' + self.cId + self.param;
      xhr.open('GET', url, true);
      xhr.send(null);
      self.fireEvent('revival', self.url, self.cId, self);
    }
  },
  start: function (url, param) {
    var self = this;
    setTimeout(function () {
      if (!self.url && !url) {
        throw new Error(self.emptyUrlError);
      }
      if (self.running) {
        return;
      }
      if (url) {
        self.url = url;
      }
      if (param && JS.isString(param)) {
        if (param.charAt(0) != '&') {
          param = '&' + param;
        }
        self.param = param;
      }
      if (self.fireEvent('beforeConnect', self.url, self) === false) {
        return;
      }
      self.running = true;
      self.startConnect();
    }, 1000);
  },
  stop: function (cause) {
    if (!this.running) {
      return;
    }
    if (this.fireEvent('beforeStop', cause, this.cId, this.url, this) === false) {
      return;
    }
    this.running = false;
    var cId = this.cId;
    this.cId = '';
    this.param = '';
    this.adml = [
    ];
    this.workStyle = '';
    try {
      this._xhr.abort();
    } catch (e) {
    };
    this.fireEvent('stop', cause, cId, this.url, this);
  },
  getId: function () {
    return this.cId;
  }
});
JS.ns('JS.Engine');
JS.Engine = (function () {
  var Engine = JS.extend(JS.Observable, {
    lStore: [
    ],
    running: false,
    connector: null,
    constructor: function () {
      this.addEvents(['start',
      'stop']);
      Engine.superclass.constructor.apply(this, arguments);
      this.connector = new JS.Connector();
      this.initEvent();
    },
    addListener: function (eventName, fn, scope, o) {
      if (this.running) {
        Engine.superclass.addListener.apply(this, arguments);
      } else {
        this.lStore.push({
          eventName: eventName,
          fn: fn,
          scope: scope,
          o: o
        });
      }
    },
    initEvent: function () {
      var self = this;
      this.connector.on({
        connect: function (cId, aml, conn) {
          self.running = true;
          self.addEvents(aml);
          for (var i = 0, len = self.lStore.length; i < len; i++) {
            var e = self.lStore[i];
            self.addListener(e.eventName, e.fn, e.scope);
          }
          self.fireEvent('start', cId, aml, self);
        },
        stop: function (cause, cId, url, conn) {
          self.running = false;
          self.fireEvent('stop', cause, cId, url, self);
          self.clearListeners();
        },
        message: function (amk, data, time) {
          self.fireEvent(amk, data, time, self);
        }
      });
    },
    start: function (url) {
      if (this.running) {
        return;
      }
      this.connector.start(url);
    },
    stop: function (cause) {
      if (!this.running) {
        return;
      }
      this.connector.stop(cause);
    },
    getConnector: function () {
      return this.connector;
    },
    getId: function () {
      return this.connector.cId;
    }
  });
  return new Engine();
}());

连接断开过程

  • Client Start
    Client run start()
    Client:start
    server: Dying--> Connect-->Revival

  • Time interval death reactivating
    After time interval
    Server:Dying-->Revival
    注:可以当心跳使用,不需要单独的创建心跳机制

  • Client Stop
    Client run Stop()
    Client:stop
    Server:drop

  • Network Error
    After time interval
    Client:stop
    Server:Dying-->drop

测试代码

监听方法一部分外部类继承然后添加,一部分直接使用匿名类,外部的没有贴代码,基本一样

package com.jony.hcsdemo2;

import org.comet4j.core.CometContext;
import org.comet4j.core.CometEngine;
import org.comet4j.core.event.DyingEvent;
import org.comet4j.core.event.RemovedEvent;
import org.comet4j.core.event.RevivalEvent;
import org.comet4j.core.listener.DyingListener;
import org.comet4j.core.listener.RemovedListener;
import org.comet4j.core.listener.RevivalListener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * Created by jony on 11/24/17.
 */
public class ConnectListener implements ServletContextListener{


//   控制指令频道
    private static final String CONTROL_CHANNEL = "control";

    /**
     * 初始化上下文
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {

        //Comet4J上下文,负责初始化配置、引擎对象、连接器对象、消息缓存等。
        CometContext cc = CometContext.getInstance();

        // 注册频道,即标识哪些字段可用当成频道,用来作为向前台传送数据的“通道”
        cc.registChannel(CONTROL_CHANNEL);


        CometEngine engine = cc.getEngine();
        engine.addConnectListener(new JoinListener());
        engine.addDropListener(new MyDropListener());

        engine.addRevivalListener(new RevivalListener() {
            @Override
            public boolean handleEvent(RevivalEvent revivalEvent) {

                System.out.println("Client Revival: "+revivalEvent.getConn().getId());
                return true;
            }
        });

        engine.addRemovedListener(new RemovedListener() {
            @Override
            public boolean handleEvent(RemovedEvent removedEvent) {

                System.out.println("Client Removed: "+removedEvent.getConn().getId());
                return true;
            }
        });

        engine.addDyingListener(new DyingListener() {
            @Override
            public boolean handleEvent(DyingEvent dyingEvent) {

                System.out.println("Client Dying: "+dyingEvent.getConn().getId());
                return false;
            }
        });
    }


    /**
     * 销毁
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

$(document).ready(function () {


    setTimeout(cometProcess);
});

function startConn() {
    JS.Engine.start("conn");

    // setTimeout(cometProcess);
    // cometProcess();
}

function dropConn() {
    JS.Engine.stop("client request drop connect");
}

function cometProcess() {
    // JS.Engine.on('start',function(cId, channelList, engine){
    //     alert('连接已建立,连接ID为:' + cId);
    // });
    // JS.Engine.on('stop',function(cause, cId, url, engine){
    //     alert('连接已断开,连接ID为:' + cId + ',断开原因:' + cause + ',断开的连接地址:'+ url);
    // });
    
    JS.Engine.on({
        start : function (cId, channelList, engine) {
            // alert('连接已建立,连接ID为:' + cId);
            $("#status").text('连接已建立,连接ID为:' + cId);
        },
        stop : function (cause, cId, url, engine) {
            // alert('连接已断开,连接ID为:' + cId + ',断开原因:' + cause + ',断开的连接地址:'+ url);
            $("#status").text('连接已断开,连接ID为:' + cId + ',断开原因:' + cause + ',断开的连接地址:'+ url);
        },
        control: function (str) {
            // alert(str);
            $("#msgShow").append(str+"<br>");
        }
        
    });
}
web.xml 配置,服务器当然也要配置,更换为NOI连接,上面
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    
    <!--配置监听器-->
    <listener>
        <description>Comet Listener</description>
        <listener-class>org.comet4j.core.CometAppListener</listener-class>
    </listener>
    
    <listener>
        <description>Connect Listener</description>
        <listener-class>com.jony.hcsdemo2.ConnectListener</listener-class>
    </listener>
    
    
    
    <!--配置访问入口-->
    <servlet>
        <servlet-name>CometServlet</servlet-name>
        <servlet-class>org.comet4j.core.CometServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>CometServlet</servlet-name>
        <url-pattern>/conn</url-pattern>
    </servlet-mapping>
    
    
    
    
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    
</web-app>

comet 4j lib 里面最常用的一些,算是可以应付基本使用,Google上面的API 文档链接也打不开,大家只能直接打开lib 研究了,当然有找到资源的还请分享下

image.png

相关文章

  • comet4j

    初步学习可以看看java web 服务器推送技术--comet4jComet HTTP服务器推送框架之Comet4...

  • web前端推送comet4j的使用

    前言 最近由于工作需要,需要进行web前端的推送方面的调研。由于前端需要对应IE8,所以经过调研选择了comet4...

网友评论

    本文标题:comet4j

    本文链接:https://www.haomeiwen.com/subject/fwnlvxtx.html