Tigase 发送消息的流程源码分析
消息节有五种不同的类型,通过 type 属性来进行区分:例如 chat 类型为 chat 的消息在两个实体间的实时对话中交换,例如两个朋友之间的即时通讯聊天。除了 type 属性外,消息节还包括一个 to 和 from 地址,并且也可以包含一个用于跟踪目的的 id 属性(我们在使用更为广泛的 IQ 节中详细的讨论 IDs)。to 地址是预期接收人的
JabberID,from 地址是发送者的JabberID。from 地址不由发送客户端提供,而是由发送者的服务器添加邮戳,以避免地址欺骗。
public void setProperties(Map<String, Object> props){
for (String name : msgrcv_names) {
mr = conf.getMsgRcvInstance(name);
if (mr instanceof MessageReceiver) {
((MessageReceiver) mr).setParent(this);
((MessageReceiver) mr).start();
}
}
}
1、当客户端发送的message消息到tigase服务端,每个一SOCKET连接都会被包装成IOService对象,IOService包含一系列操作socket的方法(接收发送数据等),processSocketData()接收网络数据,由tigase.net处理解析成xml对象,并将packet放到接收队列receivedPackets中再调用serviceListener.packetsReady(this)。由于ConnectionManager实现IOServiceListener接口,实现上调用的的是ConnectionManager中的packetsReady()来开始处理数据
此时: packetFrom=c2s@llooper/192.168.0.33_5222_192.168.0.33_38624, packetTo=sess-man@llooper
ClientConnectionManager.processSocketData(XMPPIOService<Object>serv)
JID id = serv.getConnectionId(); //c2s@llooper/192.168.0.33_5222_192.168.0.33_38624
p.setPacketFrom(id); //packetFrom 设置为onnectionId
p.setPacketTo(serv.getDataReceiver()); //packetTo 设置为sess-man --> SessionManager
addOutPacket(p);//将会委托给父 MessageRouter 路由 }
packet = (tigase.server.Message) from=c2s@llooper/192.168.0.33_5222_192.168.0.33_38624, to=sess-man@llooper, DATA=<message xmlns="jabber:client" id="44grM-176" type="chat" to="llooper@llooper"><thread>SWjZv5</thread><composing xmlns="http://jabber.org/protocol/chatstates"/></message>, SIZE=170, XMLNS=jabber:client, PRIORITY=NORMAL, PERMISSION=NONE, TYPE=chat
//我们不会处理没有目标地址的数据包,只是丢弃它们并写一个日志消息
if (packet.getTo() == null) {
log.log(Level.WARNING, "Packet with TO attribute set to NULL: {0}", packet);
return;
} //它不是一个服务发现包,我们必须找到一个处理组件
//下面的代码块是“快速”找到一个组件if //这个包TO 组件ID,格式在以下一项:
// 1。组件名+“@”+默认域名
// 2。组件名+“@”+任何虚拟主机名
// 3。组件名+ "."+默认域名
// 4。组件名+ "."+任何虚拟主机名 ServerComponent comp = getLocalComponent(packet.getTo()); //SessionManager
comp.processPacket(packet, results);
3、SessionManager.processPacket(final Packet packet)处理,有要代码如下。 例如A->B,这样做的目的是为了首先确定用户A有权限发送packet,然后是确定用户B有权限接收数据。如果用户B不在线,那么离线消息处理器会把packet保存到数据库当中。
//XMPPResourceConnection session——用户会话保存所有用户会话数据,并提供对用户数据存储库的访问。它只允许在会话的生命周期内将信息存储在永久存储或内存中。如果在分组处理时没有联机用户会话,则此参数可以为空。
XMPPResourceConnection conn = getXMPPResourceConnection(packet);
//现在要走SessionManager的处理函数,主要是走插件流程,插件在Tigase中也是一个重要的组成,入口就是在这里,SM plugin
processPacket(packet, conn);
插入下SM plugin 流程说明 :
会话管理器(SessionManager)必须对数据包进行两次处理。第一次以用户A的名义将其作为传出包进行处理,第二次以用户B的名义将其作为传入包进行处理。
这是为了确保用户A有权限发送一个包,所有的processor都应用到packet上,也为了确保用户B有权限接收packet,所有的processor都应用到packet了。例如,如果用户B是脱机的,那么有一个脱机消息processor应该将包发送到数据库,而不是用户B。
protected XMPPResourceConnection getXMPPResourceConnection(Packet p) {
XMPPResourceConnection conn = null; //首先根据这个包的发起者,来查找他的连接资源类,找不到则找接收者的资源类
JID from = p.getPacketFrom();
if (from != null) {
conn = connectionsByFrom.get(from);
if (conn != null) {
return conn;
}
} //这个接收者它可能是这个服务器上某个用户的消息,让我们为这个用户查找已建立的会话
JID to = p.getStanzaTo(); if (to != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Searching for resource connection for: " + to);
}
conn = getResourceConnection(to);
} else { // Hm, not sure what should I do now....
// Maybe I should treat it as message to admin....
log.log(Level.INFO,
"Message without TO attribute set, don''t know what to do wih this: {0}", p);
} // end of else return conn;
} protected void processPacket(Packet packet, XMPPResourceConnection conn) { ...
packet.setPacketTo(getComponentId()); //sess-man@llooper
... if (!stop) {
//授权匹配的processor处理packet
walk(packet, conn);
try {
if ((conn != null) && conn.getConnectionId().equals(packet.getPacketFrom())) {
handleLocalPacket(packet, conn);
}
} catch (NoConnectionIdException ex) {
...
}
} ...
}
packetTo被设置为组件ID(sess-man@llooper),其值原先也是这个。
其中walk(packet, conn)方法,匹配处理器(授权)。对于message,此处匹配到的processor是amp和message-carbons,message-carbons没有怎么处理,主要是amp在处理,packet被塞amp的队列中等待处理。
private void walk(final Packet packet, final XMPPResourceConnection connection) { for (XMPPProcessorIfc proc_t : processors.values()) {
XMPPProcessorIfc processor = proc_t;
//根据element和xmlns,授权匹配成功的processor
Authorization result = processor.canHandle(packet, connection); if (result == Authorization.AUTHORIZED) {
.... ProcessingThreads pt = workerThreads.get(processor.id()); if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
//packet 放到(addItem)授权了的processor的队列
if (pt.addItem(processor, packet, connection)) {
packet.processedBy(processor.id());
} else { ...
}
} else {
...
}
}
}
@Override
public void process(QueueItem item) { XMPPProcessorIfc processor = item.getProcessor(); try {
//由授权的 processor 处理 packet
processor.process(item.getPacket(), item.getConn(), naUserRepository,local_results, plugin_config.get(processor.id()));
if (item.getConn() != null) {
setPermissions(item.getConn(), local_results);
}
addOutPackets(item.getPacket(), item.getConn(), local_results);
} catch (PacketErrorTypeException e) {
...
} catch (XMPPException e) {
...
}
} //其中processor.process()------> MessageAmp.process(),如下: @Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue results, Map settings) throws XMPPException {
if (packet.getElemName() == "presence") {
... } else {
Element amp = packet.getElement().getChild("amp", XMLNS); if ((amp == null) || (amp.getAttributeStaticStr("status") != null)) {
messageProcessor.process(packet, session, repo, results, settings);
} else {
...
}
} // 其中messageProcessor.process() --------> Message.process(),如下 @Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue results, Map settings) throws XMPPException { ...
try {
...
// 在比较JIDs之前,记住要去除资源部分
id = (packet.getStanzaFrom() != null)
? packet.getStanzaFrom().getBareJID()
: null; // 检查这是否是来自客户端的数据包
if (session.isUserId(id)) {
// 这是来自这个客户端的数据包,最简单的操作是转发到它的目的地:
// Simple clone the XML element and....
// ... putting it to results queue is enough
results.offer(packet.copyElementOnly()); return;
} } catch (NotAuthorizedException e) {
...
} // end of try-catch
}
此时投递的packet :packetFrom=null,packetTo=null。
此时packet::packetFrom=sess-man@llooper,packetTo=null。
4、上层组件MessageRouter处理,把packet塞到in_queues. 又回到了MessageRouter.processPacket(Packet packet)处理:
不同的是 PacketTo为空,packet.getTo()的返回值是stanzaTo。
getLocalComponent(packet.getTo());方法根据stanzaTo与compId、comp name、Component都匹配不到。
此时packet会给组件SessionManager处理,Packet will be processed by: sess-man@llooper,由AbstractMessageReceiver的非阻塞性方法addPacketNB(Packet packet)加入到in_queues。
conn = connectionsByFrom.get(from)返回值是null,所以是根据stanzaTo取获取接收方的session,返回接收方连接的Connection。
protected XMPPResourceConnection getXMPPResourceConnection(Packet p) {
XMPPResourceConnection conn = null;
JID from = p.getPacketFrom(); if (from != null) {
conn = connectionsByFrom.get(from);
if (conn != null) {
return conn;
}
} // It might be a message _to_ some user on this server
// so let's look for established session for this user...
JID to = p.getStanzaTo(); if (to != null) {
...
conn = getResourceConnection(to);
} else { ...
} // end of else return conn;
}
6、如同步骤3,此时packet作为一个以用户B的名义将其作为传入包进行处理。
然后packetTo被设置为组件ID(sess-man@llooper)
此时packet: packetFrom = sess-man@llooper,packetTo =sess-man@llooper。
之后packet又经walk(packet, conn)方法,匹配处理器(授权),扔给amp处理。
@Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue<Packet> results, Map<String, Object> settings) throws XMPPException { // For performance reasons it is better to do the check
// before calling logging method.
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing packet: {0}, for session: {1}", new Object[] {
packet,
session });
} // You may want to skip processing completely if the user is offline.
if (session == null) {
processOfflineUser( packet, results );
return;
} // end of if (session == null)
try { // Remember to cut the resource part off before comparing JIDs
BareJID id = (packet.getStanzaTo() != null)
? packet.getStanzaTo().getBareJID()
: null; // Checking if this is a packet TO the owner of the session
if (session.isUserId(id)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Message 'to' this user, packet: {0}, for session: {1}",
new Object[] { packet,
session });
} if (packet.getStanzaFrom() != null && session.isUserId(packet.getStanzaFrom().getBareJID())) {
JID connectionId = session.getConnectionId();
if (connectionId.equals(packet.getPacketFrom())) {
results.offer(packet.copyElementOnly());
// this would cause message packet to be stored in offline storage and will not
// send recipient-unavailable error but it will behave the same as a message to
// unavailable resources from other sessions or servers
return;
}
} // Yes this is message to 'this' client
List<XMPPResourceConnection> conns = new ArrayList<XMPPResourceConnection>(5); // This is where and how we set the address of the component
// which should rceive the result packet for the final delivery
// to the end-user. In most cases this is a c2s or Bosh component
// which keep the user connection.
String resource = packet.getStanzaTo().getResource(); if (resource == null) { // If the message is sent to BareJID then the message is delivered to
// all resources
conns.addAll(getConnectionsForMessageDelivery(session));
} else { // Otherwise only to the given resource or sent back as error.
XMPPResourceConnection con = session.getParentSession().getResourceForResource(
resource); if (con != null) {
conns.add(con);
}
} // MessageCarbons: message cloned to all resources? why? it should be copied only
// to resources with non negative priority!! if (conns.size() > 0) {
for (XMPPResourceConnection con : conns) {
Packet result = packet.copyElementOnly(); result.setPacketTo(con.getConnectionId()); // In most cases this might be skept, however if there is a
// problem during packet delivery an error might be sent back
result.setPacketFrom(packet.getTo()); // Don't forget to add the packet to the results queue or it
// will be lost.
results.offer(result);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Delivering message, packet: {0}, to session: {1}",
new Object[] { packet,
con });
}
}
} else {
// if there are no user connections we should process packet
// the same as with missing session (i.e. should be stored if
// has type 'chat'
processOfflineUser( packet, results );
} return;
} // end of else // Remember to cut the resource part off before comparing JIDs
id = (packet.getStanzaFrom() != null)
? packet.getStanzaFrom().getBareJID()
: null; // Checking if this is maybe packet FROM the client
if (session.isUserId(id)) { // This is a packet FROM this client, the simplest action is
// to forward it to is't destination:
// Simple clone the XML element and....
// ... putting it to results queue is enough
results.offer(packet.copyElementOnly()); return;
} // Can we really reach this place here?
// Yes, some packets don't even have from or to address.
// The best example is IQ packet which is usually a request to
// the server for some data. Such packets may not have any addresses
// And they usually require more complex processing
// This is how you check whether this is a packet FROM the user
// who is owner of the session:
JID jid = packet.getFrom(); // This test is in most cases equal to checking getElemFrom()
if (session.getConnectionId().equals(jid)) { // Do some packet specific processing here, but we are dealing
// with messages here which normally need just forwarding
Element el_result = packet.getElement().clone(); // If we are here it means FROM address was missing from the
// packet, it is a place to set it here:
el_result.setAttribute("from", session.getJID().toString()); Packet result = Packet.packetInstance(el_result, session.getJID(), packet
.getStanzaTo()); // ... putting it to results queue is enough
results.offer(result);
}
} catch (NotAuthorizedException e) {
log.log(Level.FINE, "NotAuthorizedException for packet: " + packet + " for session: " + session, e);
results.offer(Authorization.NOT_AUTHORIZED.getResponseMessage(packet,
"You must authorize session first.", true));
} // end of try-catch
}
检查stanzaTo与session匹配通过后,根据session拿到接收方所有的连接(可能多端登陆),然后Packet result = packet.copyElementOnly()生成新的packet(原packet丢弃了),并将packetTo设置为接收方连接的ConnectionId(例如:c2s@llooper/192.168.0.33_5222_192.168.0.33_38624),通过addOutPacket()方法塞到out_queue队列。
此时packet:packetFrom = sess-man@llooper,packetTo =c2s@llooper/192.168.0.33_5222_192.168.0.33_38624。
8、 组件 c2s@llooper 从queue中取出packet,分发到目的地
public void processPacket(final Packet packet) {
...
if (packet.isCommand() && (packet.getCommand() != Command.OTHER)) {
...
} else {
// 把packet 发送给客户端
if (!writePacketToSocket(packet)) { ... }
} // end of else
}
后续有时间会不断更新,欢迎加入QQ群 310790965 更多的交流
Tigase 发送消息的流程源码分析的更多相关文章
- [Android]从Launcher开始启动App流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...
- [Android]Android系统启动流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- Spring加载流程源码分析03【refresh】
前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...
- Android笔记--View绘制流程源码分析(二)
Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...
- Android笔记--View绘制流程源码分析(一)
Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- spring boot 加载web容器tomcat流程源码分析
spring boot 加载web容器tomcat流程源码分析 我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>o ...
- springboot 事务创建流程源码分析
springboot 事务创建流程源码分析 目录 springboot 事务创建流程源码分析 1. 自动加载配置 2. InfrastructureAdvisorAutoProxyCreator类 3 ...
随机推荐
- 自己的mongodb的CRUD封装
工具类:package Utils; import com.google.common.collect.Lists; import com.mongodb.MongoClient; import co ...
- SVN忽略已提交的文件(ignore,移出版本控制)
本文适用于已安装TortoiseSVN客户端的同学. 1.右键点击要忽略的文件夹或文件,鼠标移到“TortoiseSVN”,找到“Unversion and add to ignore list”,选 ...
- 微信小程序image bindload事件失效不触发
1.先上代码 <template> <div :class="['img-wrapper', className]"> <img :src=" ...
- css中的定位属性
- mass
@python青岛qq群 1.爬取豆瓣,登录一次爬取后再循环就退出登录,抓不到了: 2.用requests.session试试,只要session对象不释放,就能记住登录状态的cookie: 3.se ...
- Python爬虫与数据分析之爬虫技能:urlib库、xpath选择器、正则表达式
专栏目录: Python爬虫与数据分析之python教学视频.python源码分享,python Python爬虫与数据分析之基础教程:Python的语法.字典.元组.列表 Python爬虫与数据分析 ...
- 与大家分享学习微信小程序开发的一些心得
因为我也才开始学习微信小程序不久,下文也是现在的一时之言,大家有不同的想法也可以在评论里共同交流讨论,希望文章能给大家提供一点点帮助. 最近接触到了一些前端框架,像Vue.js,React,发现小程序 ...
- jquery调用iframe里面的方法
$(window.parent.document).contents().find("#iframename")[0].contentWindow.iframefunction() ...
- 干货!微信自动跳转默认浏览器下载app的方法!
现在微信渠道可以说是拉新最快的渠道,因为微信具备强裂变性.但是目前微信对第三方下载链接的拦截是越来越严格了,那么想要在微信内肆无忌惮地推广链接就需要用到微信跳转浏览器的接口,那如何获取该接口呢? ...
- 这就涉及到ABAQUS历史输出中各能量变量的意义
ABAQUS中,对于很多动态问题,尤其像高速冲击模拟中,对结果评价很重要的一点就是要保证模型能量守恒,这就涉及到ABAQUS历史输出中各能量变量的意义,下面最各简单整理: ALLAE:人工伪应变能,六 ...