设为首页 收藏本站
查看: 857|回复: 0

[经验分享] Jetty cometd(Continuation)学习笔记

[复制链接]

尚未签到

发表于 2017-2-26 12:44:37 | 显示全部楼层 |阅读模式
  前言:
为了更容易的掌握Jetty cometd的使用方法,本笔记通过讲解Jetty 6.0自带的chat演示程序来辅助讲解Jetty Cometd的实现。

  环境配置方法:
服务器端:
类库清单:WEB-INF/lib

  



  • jetty-6.1.9.jar
  • jetty-util-6.1.9.jar
  • servlet-api-2.5-6.1.9.jar

      (以上Jetty服务器自带)


  • cometd-api-0.9.20080221.jar
  • cometd-bayeux-6.1.9.jar

 web.xml配置:

<!-- 配置ContinuationCometdServlet, 这个是必须的。配置后就可以支持comted -->
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.mortbay.cometd.continuation.ContinuationCometdServlet</servlet-class>
<!-- 对队列的内容进行过滤 -->
<init-param>
<param-name>filters</param-name>
<param-value>/WEB-INF/filters.json</param-value>
</init-param>
<!-- 超时设置The server side poll timeout in milliseconds (default 250000). This is how long the server will
hold a reconnect request before responding. -->
<init-param>
<param-name>timeout</param-name>
<param-value>120000</param-value>
</init-param>
<!-- The client side poll timeout in milliseconds (default 0). How long a client will wait between
reconnects -->
<init-param>
<param-name>interval</param-name>
<param-value>0</param-value>
</init-param>
<!-- the client side poll timeout if multiple connections are detected from the same browser
(default 1500). -->
<init-param>
<param-name>multiFrameInterval</param-name>
<param-value>1500</param-value>
</init-param>
<!-- 0=none, 1=info, 2=debug -->
<init-param>
<param-name>logLevel</param-name>
<param-value>0</param-value>
</init-param>
<!-- If "true" then the server will accept JSON wrapped in a comment and will generate JSON wrapped
in a comment. This is a defence against Ajax Hijacking. -->
<init-param>
<param-name>JSONCommented</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>alwaysResumePoll</param-name>
<param-value>false</param-value> <!-- use true for x-site cometd -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>


filters.json内容如下:

格式如下:

{
"channels": "/**", --要过滤的队列(支持通配符)
"filter":"org.mortbay.cometd.filter.NoMarkupFilter", --使用的过滤器,实现接口dojox.cometd.DataFilter
"init"    : {} --初始化的值,调用 DataFilter.init方法传入
}


 示例内容如下:


[
{
"channels": "/**",
"filter"  : "org.mortbay.cometd.filter.NoMarkupFilter",
"init"    : {}
},
{
"channels": "/chat/*",
"filter"   : "org.mortbay.cometd.filter.RegexFilter",
"init"    : [
[ "[fF].ck","dang" ],
[ "teh ","the "]
]
},
{
"channels": "/chat/**",
"filter"   : "org.mortbay.cometd.filter.RegexFilter",
"init"    : [
[ "[Mm]icrosoft", "Micro\\$oft" ],
[ ".*tomcat.*", null ]
]
}
]
 这时,服务器端的配置就已经完成的,基本的cometd功能就可以使用了。




客户端通过dojox.cometd.init("http://127.0.0.2:8080/cometd");就可以进行连接。

代码开发:

接下来,我们要准备客户端(使用dojo来实现)


一共三个文件


  • index.html
  • chat.js
  • chat.css(不是必须)

下面来看一下这两个文件的内容(加入注释)
index.html


<html>
<head>
<title>Cometd chat</title>
<script type="text/javascript" src="../dojo/dojo/dojo.js"></script><!-- dojo类库 -->
<script type="text/javascript" src="../dojo/dojox/cometd.js.uncompressed.js"></script><!-- dojo-cometd类库 -->
<script type="text/javascript" src="chat.js"></script><!-- chat js文件,控制cometd的连接,消息的发送与接收 -->
<link rel="stylesheet" type="text/css" href="chat.css">
</head>
<body>
<h1>Cometd Chat</h1>
<div id="chatroom">
<div id="chat"></div>
<div id="input">
<div id="join" ><!-- 未登录时,显示的登录名和登录按钮 -->
Username:&nbsp;<input id="username" type="text"/>
<input id="joinB" class="button" type="submit" name="join" value="Join"/>
</div>
<div id="joined" class="hidden"><!-- 登录后,显示的消息框和发送,退出按钮(默认为隐藏) -->
Chat:&nbsp;<input id="phrase" type="text"></input>
<input id="sendB" class="button" type="submit" name="join" value="Send"/>
<input id="leaveB" class="button" type="submit" name="join" value="Leave"/>
</div>
</div>
</div>
</body>
</html>


 chat.js文件


//引入所需要的类
dojo.require("dojox.cometd");
dojo.require("dojox.cometd.timestamp");
//定义一个room类
var room = {
//定义属性
_last: "", //最后发送消息的人员(如果不是本人,则显示为空)
_username: null, //当前的用户名
_connected: true, //当前的连接状态 true已经连接, false表示未连接
groupName: "whimsical", //组名(未知)
//登录操作
join: function(name){
if(name == null || name.length==0 ){
alert('Please enter a username!');
}else{
dojox.cometd.init(
new String(document.location).replace(/http:\/\/[^\/]*/,'').replace(/\/examples\/.*$/,'')+"/cometd");
// dojox.cometd.init("http://127.0.0.2:8080/cometd");
this._connected = true;
this._username = name;
dojo.byId('join').className='hidden';
dojo.byId('joined').className='';
dojo.byId('phrase').focus();
// subscribe and join
dojox.cometd.startBatch();
dojox.cometd.subscribe("/chat/demo", room, "_chat", { groupName: this.groupName});
dojox.cometd.publish("/chat/demo", {
user: room._username,
join: true,
chat : room._username+" has joined"
}, { groupName: this.groupName });
dojox.cometd.endBatch();
// handle cometd failures while in the room
room._meta = dojo.subscribe("/cometd/meta", this, function(event){
console.debug(event);   
if(event.action == "handshake"){
room._chat({ data: {
join: true,
user:"SERVER",
chat:"reinitialized"
} });
dojox.cometd.subscribe("/chat/demo", room, "_chat", { groupName: this.groupName });
}else if(event.action == "connect"){
if(event.successful && !this._connected){
room._chat({ data: {
leave: true,
user: "SERVER",
chat: "reconnected!"
} });
}
if(!event.successful && this._connected){
room._chat({ data: {
leave: true,
user: "SERVER",
chat: "disconnected!"
} });
}
this._connected = event.successful;
}
}, {groupName: this.groupName });
}
},
//离开操作
leave: function(){
if(!room._username){
return;
}
if(room._meta){
dojo.unsubscribe(room._meta, null, null, { groupName: this.groupName });
}
room._meta=null;
dojox.cometd.startBatch();
dojox.cometd.unsubscribe("/chat/demo", room, "_chat", { groupName: this.groupName });
dojox.cometd.publish("/chat/demo", {
user: room._username,
leave: true,
chat : room._username+" has left"
}, { groupName: this.groupName });
dojox.cometd.endBatch();
// switch the input form
dojo.byId('join').className='';
dojo.byId('joined').className='hidden';
dojo.byId('username').focus();
room._username = null;
dojox.cometd.disconnect();
},
//发送消息
chat: function(text){
if(!text || !text.length){
return false;
}
dojox.cometd.publish("/chat/demo", { user: room._username, chat: text}, { groupName: this.groupName });
},
//从服务器收到消息后,回调的方法
_chat: function(message){
var chat=dojo.byId('chat');
if(!message.data){
console.debug("bad message format "+message);
return;
}
var from=message.data.user;
var special=message.data.join || message.data.leave;
var text=message.data.chat;
if(!text){ return; }
if( !special && from == room._last ){
from="";
}else{
room._last=from;
from+=":";
}
if(special){
chat.innerHTML += "<span class=\"alert\"><span class=\"from\">"+from+"&nbsp;
</span><span class=\"text\">"+text+"</span></span><br/>";
room._last="";
}else{
chat.innerHTML += "<span class=\"from\">"+from+"&nbsp;</span><span class=\"text\">"+text+"</span><br/>";
}
chat.scrollTop = chat.scrollHeight - chat.clientHeight;   
},
//初始操作
_init: function(){
dojo.byId('join').className='';
dojo.byId('joined').className='hidden';
dojo.byId('username').focus();
var element=dojo.byId('username');
element.setAttribute("autocomplete","OFF");
dojo.connect(element, "onkeyup", function(e){ //支持回车,登录  
if(e.keyCode == dojo.keys.ENTER){
room.join(dojo.byId('username').value);
return false;
}
return true;
});
dojo.connect(dojo.byId('joinB'), "onclick", function(e){ //绑定 room.join方法到 Join按扭
room.join(dojo.byId('username').value);
e.preventDefault();
});
element=dojo.byId('phrase');//取得消息框
element.setAttribute("autocomplete","OFF");
dojo.connect(element, "onkeyup", function(e){ //支持回车发送消息
if(e.keyCode == dojo.keys.ENTER){
room.chat(dojo.byId('phrase').value);
dojo.byId('phrase').value='';
e.preventDefault();
}
});
dojo.connect(dojo.byId('sendB'), "onclick", function(e){  //绑定 room.chat方法到 sendB按扭
room.chat(dojo.byId('phrase').value);
dojo.byId('phrase').value='';
});
dojo.connect(dojo.byId('leaveB'), "onclick", room, "leave"); //绑定 room.leave方法到 leaveB按扭
}
};
//页面装载时,调用room._init方法
dojo.addOnLoad(room, "_init");
//页面关闭时,调用 room.leave方法
dojo.addOnUnload(room,"leave");
//vim:ts=4:noet:
 补充:服务器端如何监控消息队列,以及进行订阅,发送消息操作





要进行 监控消息队列,以及进行订阅,发送消息操作的关键就是取得 Bayeux接口实现类 的实例

可以通过 ServletContextAttributeListener 这个监听器接口,通过attributeAdded方式加入

实现方法如下:


public class BayeuxStartupListener implements ServletContextAttributeListener{
public void initialize(Bayeux bayeux)     {
synchronized(bayeux) {
if (!bayeux.hasChannel("/service/echo")){
//取得 bayeux实例               
}
}
}
public void attributeAdded(ServletContextAttributeEvent scab){
if (scab.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX)) {
Bayeux bayeux=(Bayeux) scab.getValue();
initialize(bayeux);
}
}
public void attributeRemoved(ServletContextAttributeEvent scab)
{
}
public void attributeReplaced(ServletContextAttributeEvent scab){
}
}


 取到 Bayeux实例后,就可以借助BayeuxService类帮我们实现消息队列的监听,订阅消息以及发送消息


public void initialize(Bayeux bayeux){
synchronized(bayeux){
if (!bayeux.hasChannel("/service/echo")){
//取得 bayeux实例  
new ChatService(bayeux);            
}
}
}


 具体方法请看下面这段代码:


//定义 ChatService类,继承 BayeuxService
public static class ChatService extends BayeuxService {
ConcurrentMap<String,Set<String>> _members = new ConcurrentHashMap<String,Set<String>>();
public ChatService(Bayeux bayeux){
super(bayeux, "chat");//必须,把 Bayeux传入到 BayeuxService对象中
subscribe("/chat/**", "trackMembers"); //订阅队列,收到消息后,会回调trackMembers方法
/*
subscribe支持回调的方法如下:
# myMethod(Client fromClient, Object data)
# myMethod(Client fromClient, Object data, String id)
# myMethod(Client fromClient, String channel, Object data,String id)
# myMethod(Client fromClient, Message message)
参数:
Client fromClient 发送消息的客户端
Object data 消息内容
id The id of the message
channel 队列名称
Message message 消息对象。继承于Map
*/
}
//发布消息到队列
public void sendMessage(String message) {
Map<String,Object> mydata = new HashMap<String, Object>();
mydata.put("chat", message);
Client sender = getBayeux().newClient("server");
getBayeux().getChannel("/chat/demo", false).publish(sender, mydata, "0"/*null*/);
}
//发送消息给指定的client(非广播方式)
public void sendMessageToClient(Client joiner, String message) {
Map<String,Object> mydata = new HashMap<String, Object>();
mydata.put("chat", message);
send(joiner, "/chat/demo", mydata, "0"/*null*/);
}   
//订阅消息回调方法
public void trackMembers(Client joiner, String channel, Map<String,Object> data, String id){
//解释消息内容,如果消息内容中 有 join这个字段且值为true
if (Boolean.TRUE.equals(data.get("join"))){
//根据队列,取得当前登录的人员
Set<String> m = _members.get(channel);
if (m==null){
//如果为空,则创建一个新的Set实现
Set<String> new_list=new CopyOnWriteArraySet<String>();
m=_members.putIfAbsent(channel,new_list);
if (m==null)
m=new_list;
}
final Set<String> members=m;
final String username=(String)data.get("user");
members.add(username);
//为该client增加事件,Remove事件。当用户退出时,触发该方法。            
joiner.addListener(new RemoveListener(){
public void removed(String clientId, boolean timeout){
members.remove(username);
}
});
//为该client增加事件,消息的发送和接收事件。当用户退出时,触发该方法。
joiner.addListener(new MessageListener() {
public void deliver(Client fromClient, Client toClient, Message message) {
System.out.println("message from " + fromClient.getId() + " to "
+ toClient.getId() + " message is " + message.getData());
}      
});
Map<String,Object> mydata = new HashMap<String, Object>();
mydata.put("chat", "members=" + members);
//把已经登录的人员信息列表,发送回给消息发送者
send(joiner,channel,mydata,id);
}
}
}
 附:部分使用到的API文档

  dojox.cometd.subscribe
dojo.require("dojox.cometd._base");
defined in dojox/cometd/_base.js

dojox.cometd.subscribe() handles all the hard work of telling the server that we want to be notified when events are published on a particular topic. subscribe accepts a function to handle messages and returns a dojo.Deferred object which has an extra property added to it which makes it suitable for passing to dojox.cometd.unsubscribe() as a “subscription handle” (much like the handle object that dojo.connect() produces and which dojo.disconnect() expects).

Note that of a subscription is registered before a connection with the server is established, events sent before the connection is established will not be delivered to this client. The deferred object which subscribe returns will callback when the server successfuly acknolwedges receipt of our “subscribe” request.
Usage:

  


var foo=dojox.cometd.subscribe(channel: String, objOrFunc: Object, funcName: String, props: Object?); (view source)
props = props||{};
if(objOrFunc){
var tname = "/cometd"+channel;
var subs = this._subscriptions[tname];
if(!subs || subs.length==0){
subs = [];
props.channel = "/meta/subscribe";
props.subscription = channel;
this._sendMessage(props);

var _ds = this._deferredSubscribes;
if(_ds[channel]){
_ds[channel].cancel();
delete _ds[channel];
}
_ds[channel] = new dojo.Deferred();
}

for(var i in subs){
if( subs.objOrFunc === objOrFunc && (!subs.funcName&&!funcName||subs.funcName==funcName) ){
return null;
}
}

var topic = dojo.subscribe(tname, objOrFunc, funcName);
subs.push({
topic: topic,
objOrFunc: objOrFunc,
funcName: funcName
});
this._subscriptions[tname] = subs;
}
var ret = this._deferredSubscribes[channel]||{};
ret.args = dojo._toArray(arguments);
return ret; // dojo.Deferred

parameter    type    description
channel     String      
objOrFunc     Object     an object scope for funcName or the name or reference to a function to be called when messages are delivered to the
funcName     String     the second half of the objOrFunc/funcName pair for identifying a callback function to notifiy upon channel message delivery
props     Object     Optional.
Examples
Example 1
Simple subscribe use-case
dojox.cometd.init("http://myserver.com:8080/cometd");
// log out all incoming messages on /foo/bar
dojox.cometd.subscribe("/foo/bar", console, "debug");
Example 2
Subscribe before connection is initialized
dojox.cometd.subscribe("/foo/bar", console, "debug");
dojox.cometd.init("http://myserver.com:8080/cometd");
Example 3
Subscribe an unsubscribe
dojox.cometd.init("http://myserver.com:8080/cometd");
var h = dojox.cometd.subscribe("/foo/bar", console, "debug");
dojox.cometd.unsubscribe(h);
Example 4
Listen for successful subscription:
dojox.cometd.init("http://myserver.com:8080/cometd");
var h = dojox.cometd.subscribe("/foo/bar", console, "debug");
h.addCallback(function(){
console.debug("subscription to /foo/bar established");
});

dojox.cometd.publish
dojo.require("dojox.cometd._base");
defined in dojox/cometd/_base.js
publishes the passed message to the cometd server for delivery on the specified topic
Usage
var foo=dojox.cometd.publish(channel: String, data: Object, props: Object?); (view source)
var message = {
data: data,
channel: channel
};
if(props){
dojo.mixin(message, props);
}
this._sendMessage(message);

parameter    type    description
channel     String     the destination channel for the message
data     Object     a JSON object containing the message "payload" properties: Optional. Other meta-data to be mixed into the top-level of the message
props     Object     Optional.
 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-347470-1-1.html 上篇帖子: Maven Jetty Plugin 配置指南(三) 下篇帖子: Jetty 6.1.6版本泄露cookie的bug
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表