写在前面:之前是做openfire,现转弄tigase,对tigase管道流的作风还不是很了解。简单整理了下Message的流程,不妥之处,还请各位指正。 该地址有些tigae官网的翻译文章:https://chutianxing.wordpress.com/tag/tigase/page/2/ 可能需要翻墙 建议学习tigae流程时多看日志 init.properties文件里的日志开关 --debug=server (表示记录tigase.server包里的日志),多个包用逗号分隔,例如:--debug=server,xmpp,net 1. tigase服务端接收处理客户端发送的message的类都在tigase.io和tigase.net。tigase.io更底层,接收网络数据,由tigase.net处理解析成xml对象,并将packet放到接收队列receivedPackets中。 此时的packet :packetFrom=null,packetTo=null。 (packet的属性里面有packetFrom、packetTo 这是组件间传递用的地址,而stanzaFrom、stanzaTo是客户端或者muc的地址) @Override public IOService call() throws IOException { writeData(null); boolean readLock = true; if (stopping) { stop(); } else { readLock = readInProgress.tryLock(); if (readLock) { try { //解析成XML并将packet放入receivedPackets接收队列 processSocketData(); if ((receivedPackets() > 0) && (serviceListener != null)) { //给packet设置一些属性,并放到被QueueListener监听的队列中 serviceListener.packetsReady(this); } // end of if (receivedPackets.size() > 0) } finally { readInProgress.unlock(); } } } return readLock ? this : null; } 其中serviceListener.packetsReady(this) ----> ConnectionManager.packetsReady ----> writePacketsToSocket(serv, processSocketData(serv)); ---->ClientConnectionManager.processSocketData(XMPPIOService<Object>serv), 最终在processSocketData方法中设置packet的一些属性,通过addOutPacket(Packet packet)方法将packet放入out_queues队列。 @Override public Queue processSocketData(XMPPIOService serv) { JID id = serv.getConnectionId(); Packet p = null; while ((p = serv.getReceivedPackets().poll()) != null) { ... if (p.getAttributeStaticStr(Packet.XMLNS_ATT) == null) { p.setXMLNS(XMLNS); ..... } p.setPacketFrom(id); JID receiver = serv.getDataReceiver(); if (receiver != null) { p.setPacketTo(serv.getDataReceiver()); addOutPacket(p); } else { ... } // TODO: Implement sending 'req' attributes by the server too } // end of while () return null; } 将 connectionId : c2s@user-20140808fs/127.0.0.1_5222_127.0.0.1_56591 设置为packetFrom,组件sess_man的jidsess-man@user-20140808fs设置为packetTo,以及设置XMLNS=jabber:client。 此时packet: from=c2s@user-20140808fs/127.0.0.1_5222_127.0.0.1_54883 ,to=sess-man@user-20140808fs 2. packet 的进出都被AbstractMessageReceiver的内部类QueueListener监听,并加入到 queue队列中,run()方法从queue队列中取出packet,根据type分发。 private class QueueListener extends Thread { ... private PriorityQueueAbstract queue; ... @Override public void run() { ... while (!threadStopped) { try { // 现在处理下一个等待的packet packet = queue.take(); ++packetCounter; switch (type) { case IN_QUEUE : ... if (task != null) { task.handleResponse(packet); } else { ... if (!processed && ((packet = filterPacket(packet, incoming_filters)) != null)) { processPacket(packet); } ... } break; case OUT_QUEUE : if ((packet = filterPacket(packet, outgoing_filters)) != null) { processOutPacket(packet); } break; default : log.log(Level.SEVERE, "Unknown queue element type: {0}", type); break; } // end of switch (qel.type) } catch (InterruptedException e) { ... } catch (Exception e) { ... } // end of try-catch } // end of while (! threadStopped) } } 3. packet 由case OUT_QUEUE分支 的 processOutPacket(packet) 分发,交给上层组件MessageRouter处理,MessageRouter把packet塞到in_queues packet被塞到out_queue和in_queue的动作都被QueueListener监听,并加上type放到自己的queue中,等待处理。 4. packet 被从queue取出,经CASE IN_QUEUE分支的processPacket(packet)分发。 MessageRouter.processPacket(Packet packet)部分代码如下: @Override public void processPacket(Packet packet) { //getTo 方法取pcketTo的值,若为空,则取stanzaTo( 中的to) if (packet.getTo() == null) { return; } ... //匹配分发组件 ServerComponent comp = getLocalComponent(packet.getTo()); if (comp != null) { Queue results = new ArrayDeque (); if (comp == this) { processPacketMR(packet, results); } else { ... comp.processPacket(packet, results); } ... return; } ... } 根据to=sess-man@user-20140808fs匹配, Packet will be processed by:sess-man@user-20140808fs,由AbstractMessageReceiver的非阻塞性方法addPacketNB(Packet packet)加入到in_queues。 5. 组件sess-man@user-20140808fs从queue拿到packet由SessionManager.processPacket(final Packet packet)处理。 @Override public void processPacket(final Packet packet) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Received packet: {0}", packet.toStringSecure()); } ... XMPPResourceConnection conn = getXMPPResourceConnection(packet); ... processPacket(packet, conn); } 其中getXMPPResourceConnection(packet)方法,返回conn。packet再由SessionManager.processPacket(packet, conn)处理。 代码: protected void processPacket(Packet packet, XMPPResourceConnection conn) { ... packet.setPacketTo(getComponentId()); ... 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@user-20140808fs),其值原先也是这个。 其中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 { ... } } } 6. WorkerThread.run() 从队列中取出packet,由SessionManager.process(QueueItem item)给amp处理。 SessionManager.pocess(QueueItem item) 如下: @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 { ... // 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; } } catch (NotAuthorizedException e) { ... } // end of try-catch } 检查stanzaFfrom与session匹配通过后,将packet.copyElementOnly()放到results中,作后续投递,原来的packet 就丢弃了。 此时投递的packet : packetFrom=null,packetTo=null。 packet在SessionManager.addOutPacket(Packet packet)中判断packetFrom是否为空,为空则将其设置为ComponentId(此处为 sess-man@user-20140808fs),然后调用父类(AbstractMessageReceiver.java) 的addOutPacket(packet)方法塞到 out_queue 队列中。 此时packet:: packetFrom= sess-man@user-20140808fs ,packetTo=mull。 7. 如同步骤3,上层组件MessageRouter处理,把packet塞到in_queues. 8. 如同步骤4,不同的是 PacketTo为空,packet.getTo()的返回值是stanzaTo。 getLocalComponent(packet.getTo());方法根据stanzaTo与compId、comp name、Component都匹配不到。 此时packet会给组件SessionManager处理,Packet will be processed by: sess-man@user-20140808fs,由AbstractMessageReceiver的非阻塞性方法addPacketNB(Packet packet)加入到in_queues。 9. 如同步骤5, 不同的是在getXMPPResourceConnection(packet)方法中, 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; } 然后packetTo被设置为组件ID( sess-man@user-20140808fs)。 此时packet:: packetFrom = sess-man@user-20140808fs ,packetTo = sess-man@user-20140808fs。 之后packet又经walk(packet, conn)方法,匹配处理器(授权),扔给amp处理。 10.如同步骤6,直到Message.process(),如下: @Override public void process(Packet packet, XMPPResourceConnection session, NonAuthUserRepository repo, Queue results, Map settings) throws XMPPException { ... 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)) { .. // Yes this is message to 'this' client List conns = new ArrayList (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(session.getActiveSessions()); } 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); ... } } else { .... } return; } // end of else ... } catch (NotAuthorizedException e) { ... } // end of try-catch } 检查stanzaTo与session匹配通过后,根据session拿到接收方所有的连接(可能多端登陆),然后Packet result = packet.copyElementOnly()生成新的packet(原packet丢弃了),并将packetTo设置为接收方连接的ConnectionId(例如:c2s@user-20140808fs/127.0.0.1_5222_127.0.0.1_50536),通过addOutPacket()方法塞到out_queue队列。 此时packet:packetFrom = sess-man@user-20140808fs,packetTo =c2s@user-20140808fs/127.0.0.1_5222_127.0.0.1_50536。 11. 如同步骤3、4 。不同的是 根据packetTo匹配到组件 c2s@user-20140808fs。 12. 组件 c2s@user-20140808fs 从queue中取出packet,分发 public void processPacket(final Packet packet) { ... if (packet.isCommand() && (packet.getCommand() != Command.OTHER)) { ... } else { // 把packet 发送给客户端 if (!writePacketToSocket(packet)) { ... } } // end of else } ps:muc的message与此类似,一直到步骤8匹配到的分发组件是 muc@user-20140808fs,MUC组件分发的时候给每个组员生成一个packet(packetFrom=null, packetTo=null),扔到队列,接下来又是步骤6中的判断packetFrom是否为空,为空则将其设置为ComponentId()...... 附草图一张: 潜水N久,第一次写博客。。。鞠躬下台