OpenFire源码学习之二十五:消息回执与离线消息(下)

这一篇紧接着上面继续了。

方案二

基于redis的消息回执。主要流程分为下面几个步骤:

1)将消息暂存储与redis中,设置好消息的过期时间

2)客户端回执消息id来消灭暂存的消息

3)开通单独线程论坛在第1)步中的消息。根据消息的时间重新发送消息。如果消息第一次存放的时间大雨有效期(自定义10秒),解析消息中的to查找用户是否还在线。如果在则T掉(因为它长时间不理会服务的重要命令),如果不在线则将消息放置离线表。

 

OK,先来看看消息的存储格式吧。

1.MESSAGE消息 用户集合

 SADD  SOGU:[username]  [VALUE(messageID)] [VALUE(messageID)] ...

2.已读消息设备集合

 SADD  RT:[terminalid]  [VALUE(messageID)] [VALUE(messageID)] ...

3.消息内容

 HMSET  OGM:[messageID]  CREATIONDATE [VALUE]  UPDATEDATE [VALUE] STANZA [VALUE]

4.用户、设备关联

 SADD URT:[USERNAME]  [VALUE(terminalid)] .......

(先根据消息id查找时间,在java中排序后 查找stanza)

MESSAGE

--离线表

ZADD OFOFFLINE:[username]  [INDEX(时间戳)] [VALUE(messageID)] 、[VALUE]、[VALUE]......              [VALUE]

HMSET OFOFFLINE:[messageID] STANZA[VALUE]

        CREATIONDATE [VALUE]  MESSAGESIZ[VALUE]

 

将消息暂时消息存储:

[java]  view plain  copy
  1.  public void storeMessage(String username, Packet packet) {  
  2.       
  3. edis jedis = XMPPServer.getInstance().getGroupRedisManager().getJedis();  
  4.     String packetID = "";  
  5.     if (packet instanceof Message)   
  6.         packetID = ((Message)packet).getID();  
  7.         else if (packet instanceof IQ)   
  8.              packetID = ((IQ)packet).getID();  
  9.         else   
  10.             return;  
  11.       
  12.     try {  
  13.         jedis.sadd("SOGU:" + username, packetID);  
  14.         Map<String, String> hash = new HashMap<String, String>();  
  15.         hash.put("STANZA", packet.toXML());  
  16.         hash.put("CREATIONDATE", StringUtils.dateToMillis(new Date()));  
  17.         jedis.hmset("OGM:" + packetID, hash);  
  18.  finally {  
  19. XMPPServer.getInstance().getGroupRedisManager().returnRes(jedis);  
  20.   
  21.       
  22.     htp.execute(addMessagesToDB(packet));  
  23.  }  
  24.    
  25.  private Runnable addMessagesToDB(final Packet packet) {  
  26.     return new Runnable() {  
  27. @Override  
  28. public void run() {  
  29.     MyDBopt.insertMessage(packet);  
  30. }  

客户端收到消息来回执服务端的操作

[html]  view plain  copy
  1.  private void handle(IQ packet) {  
  2.     JID recipientJID = packet.getTo();  
  3.     if (IQ.Type.crs != packet.getType()) {  
  4.          // Check if the packet was sent to the server hostname  
  5.          if (recipientJID != null && recipientJID.getNode() == null &&  
  6.                  recipientJID.getResource() == null && serverName.equals(recipientJID.getDomain())) {  
  7.              Element childElement = packet.getChildElement();  
  8.              if (childElement != null && childElement.element("addresses") != null) {  
  9.                  // to route this packet  
  10.                  multicastRouter.route(packet);  
  11.                  return;  
  12.              }  
  13.          }  
  14.     }  
  15.        
  16.      if (IQ.Type.crs == packet.getType()) {  
  17.         String username = packet.getFrom().getNode();  
  18.         String terminal = packet.getFrom().getTerminal();  
  19.         String msgId = packet.getID();  
  20.         if (username == null || msgId == null || "".equals(msgId)) {  
  21.             return ;  
  22.         }  
  23.         if (terminal == null) {terminal = username + "_" + System.currentTimeMillis()%1000000; }  
  24.         Jedis jedis = XMPPServer.getInstance().getGroupRedisManager().getJedis();  
  25.           
  26.         try {  
  27.             jedis.sadd("URT:" + username, terminal);  
  28.             jedis.sadd("RT:" + terminal, packet.getID());  
  29. } finally {  
  30.     XMPPServer.getInstance().getGroupRedisManager().returnRes(jedis);  
  31. }  
  32.           
  33.         threadPool.execute(createTask(msgId, username, terminal));  
  34.         return;  
  35.      }  
  36.      if (packet.getID() != null && (IQ.Type.result == packet.getType() || IQ.Type.error == packet.getType())) {  
  37.          // The server got an answer to an IQ packet that was sent from the server  
  38.          IQResultListener iqResultListener = resultListeners.remove(packet.getID());  
  39.          if (iqResultListener != null) {  
  40.              resultTimeout.remove(packet.getID());  
  41.              if (iqResultListener != null) {  
  42.                  try {  
  43.                      iqResultListener.receivedAnswer(packet);  
  44.                  }  
  45.                  catch (Exception e) {  
  46.                      Log.error(  
  47.                              "Error processing answer of remote entity. Answer: "  
  48.                                      + packet.toXML(), e);  
  49.                  }  
  50.                  return;  
  51.              }  
  52.          }  
  53.      }  
  54.      try {  
  55.          // Check for registered components, services or remote servers  
  56.          if (recipientJID != null &&  
  57.                  (routingTable.hasComponentRoute(recipientJID) || routingTable.hasServerRoute(recipientJID))) {  
  58.              // A component/service/remote server was found that can handle the Packet  
  59.              routingTable.routePacket(recipientJID, packet, false);  
  60.              return;  
  61.          }  
  62.          if (isLocalServer(recipientJID)) {  
  63.              // Let the server handle the Packet  
  64.              Element childElement = packet.getChildElement();  
  65.              String namespace = null;  
  66.              if (childElement != null) {  
  67.                  namespace = childElement.getNamespaceURI();  
  68.              }  
  69.              if (namespace == null) {  
  70.                  if (packet.getType() != IQ.Type.result && packet.getType() != IQ.Type.error) {  
  71.                      // Do nothing. We can't handle queries outside of a valid namespace  
  72.                      Log.warn("Unknown packet " + packet.toXML());  
  73.                  }  
  74.              }  
  75.              else {  
  76.                  // Check if communication to local users is allowed  
  77.                  if (recipientJID != null && userManager.isRegisteredUser(recipientJID.getNode())) {  
  78.                      PrivacyList list =  
  79.                              PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode());  
  80.                      if (list != null && list.shouldBlockPacket(packet)) {  
  81.                          // Communication is blocked  
  82.                          if (IQ.Type.set == packet.getType() || IQ.Type.get == packet.getType()) {  
  83.                              // Answer that the service is unavailable  
  84.                              sendErrorPacket(packet, PacketError.Condition.service_unavailable);  
  85.                          }  
  86.                          return;  
  87.                      }  
  88.                  }  
  89.                  IQHandler handler = getHandler(namespace);  
  90.                  if (handler == null) {  
  91.                      if (recipientJID == null) {  
  92.                          // Answer an error since the server can't handle the requested namespace  
  93.                          sendErrorPacket(packet, PacketError.Condition.service_unavailable);  
  94.                      }  
  95.                      else if (recipientJID.getNode() == null ||  
  96.                              "".equals(recipientJID.getNode())) {  
  97.                          // Answer an error if JID is of the form <domain>  
  98.                          sendErrorPacket(packet, PacketError.Condition.feature_not_implemented);  
  99.                      }  
  100.                      else {  
  101.                          // JID is of the form <node@domain>  
  102.                          // Answer an error since the server can't handle packets sent to a node  
  103.                          sendErrorPacket(packet, PacketError.Condition.service_unavailable);  
  104.                      }  
  105.                  }  
  106.                  else {  
  107.                      handler.process(packet);  
  108.                  }  
  109.              }  
  110.          }  
  111.          else {  
  112.              // JID is of the form <node@domain/resource> or belongs to a remote server  
  113.              // or to an uninstalled component  
  114.              routingTable.routePacket(recipientJID, packet, false);  
  115.          }  
  116.      }  
  117.      catch (Exception e) {  
  118.      ......  
  119.      }  
  120.  }  

离线消息

离线消息的优化。

同样可以拓展XMPP。比如

客户端获取离线消息,可以这么通讯。

1)先向服务器询问,我总的离线消息的基本状况(有多大,有多少条)

[html]  view plain  copy
  1. <iq id="BfI3V-47" to="8ntmorv1ep4wgcy" type="get" from="test@8ntmorv1ep4wgcy">  
  2.   <query xmlns="http://jabber.org/protocol/offmsg#bif"/>  
  3. </iq>  

2)服务端返回

[html]  view plain  copy
  1. <iq type="result" id="BfI3V-47" from="8ntmorv1ep4wgcy" to="test@8ntmorv1ep4wgcy">  
  2.   <query xmlns="http://jabber.org/protocol/offmsg#bifs">  
  3.      <size>1024b</>  
  4.      <count>128</>  
  5.      <idset>1001,1002...</>  
  6.   </query>  
  7. </iq>  

3)客户端发送分批获取命令,一次给我发10条发完为止。

[html]  view plain  copy
  1. <iq id="BfI3V-47" to="8ntmorv1ep4wgcy" type="get" from="test@8ntmorv1ep4wgcy">  
  2.   <query xmlns="http://jabber.org/protocol/offmsg#start"/>  
  3.    <pagesize>10</>  
  4. </iq>  

4)服务端开始发送消息

[html]  view plain  copy
  1. <iq type="result" id="BfI3V-47" from="8ntmorv1ep4wgcy" to="test@8ntmorv1ep4wgcy">  
  2.   ......  
  3. </iq>  
  4. <iq type="result" id="BfI3V-47" from="8ntmorv1ep4wgcy" to="test@8ntmorv1ep4wgcy">  
  5.   ......  
  6. </iq>  
  7. .....  

5)告诉客户端我都发完了

[html]  view plain  copy
  1. <iq type="result" id="BfI3V-47" from="8ntmorv1ep4wgcy" to="test@8ntmorv1ep4wgcy">  
  2.   <query xmlns="http://jabber.org/protocol/offmsg#end"/>  
  3. </iq>  

6)客户端本地校验,回执已经接收到的消息

[html]  view plain  copy
  1. <message id="BfI3V-47" to="8ntmorv1ep4wgcy"   
  2.  from="test1@8ntmorv1ep4wgcy/Spark 2.6.3" type="crs"/>  

这里本人只是做了一个简单的示意想法。如果需要更加精准的不妨在仔细想想消息处理与格式。

 

离线消息存储。

将消息存储到redis中:

[java]  view plain  copy
  1. public void addMessageToRedis(Message message) {  
  2.         if (message == null) {  
  3.             return;  
  4.         }  
  5.         JID recipient = message.getTo();  
  6.         String username = recipient.getNode();  
  7.         // If the username is null (such as when an anonymous user), don't store.  
  8.         if (username == null || !UserManager.getInstance().isRegisteredUser(recipient)) {  
  9.             return;  
  10.         }  
  11.         else  
  12.         if (!XMPPServer.getInstance().getServerInfo().getXMPPDomain().equals(recipient.getDomain())) {  
  13.             // Do not store messages sent to users of remote servers  
  14.             return;  
  15.         }  
  16.         String msgXML = message.getElement().asXML();  
  17.           
  18.         Jedis jedis = XMPPServer.getInstance().getChatMessageJedisPoolManager().getJedis();  
  19.           
  20.         try {  
  21.             String newDate = StringUtils.dateToMillis(new java.util.Date());  
  22.             String id = MessageIdTactics.mid(username);  
  23.             jedis.zadd("OFOFFLINE:" + username, Long.valueOf(newDate), id   
  24.             Map<String, String> hash = new HashMap<String, String>();  
  25.             hash.put("STANZA", msgXML);  
  26.             hash.put("MESSAGESIZ", String.valueOf(msgXML.length()));  
  27.             hash.put("CREATIONDATE", newDate);  
  28.             jedis.hmset("OFOFFLINE:" + id, hash);  
  29.         } finally {  
  30.             XMPPServer.getInstance().getChatMessageJedisPoolManager().returnRes(jedis);  
  31.         }  
  32.           
  33.         if (sizeCache.containsKey(username)) {  
  34.             int size = sizeCache.get(username);  
  35.             size += msgXML.length();  
  36.             sizeCache.put(username, size);  
  37.         }  
  38.         htp.execute(addMessageToDB(message));  
  39.     }  

Redis优化这块就到这啦。主要要做的就是:

第一:存储用户或者MUC、Group等这些都需要设置消息存储的生命周期。当用户不处于活跃状态或者长时间不登陆的。要从redis中提出。免得浪费资源。当用户重新加载的时候再将他放置redis中

第二:将需要回执消息和离线消息分开。需要回执的消息需要设置他的生命周期。离线表最好做个定时器。轮询消息。将超时出现范围内的消息(比如周期为一周)的消息同步至关系表中。这里的离线消息需要将用户的设备分开来。

这里要考虑不同的设备终端等很多不同场景,问题会比较绕口。欢迎大家和我邮件交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值