Redis实现多人多聊天室功能_Redis

本文为大家分享了Redis支持多人多聊天室功能的设计代码,供大家参考,具体内容如下

设计原理

左边的一个数据域,代表两个聊天室,聊天室id分别是827,729

在聊天室827里,有2个人,分别是jason22,jeff24他们分别已经阅读过聊天室内的id为5和6的消息

右边的一个数据域,代表了用户在不同的聊天室,jason22参与了827与729聊天室,在这两个聊天室里,他分别阅读到了id为5和id为10的消息

另外827聊天室内id为5的消息与729聊天室内id为5的消息不一样。

同时还有三个域
msgs:chatid
这是一个zset,有序集合,member是消息体,score是消息id
代表的是某个聊天室内已经发出的消息
另外 这里面存的是有用的消息,已经被所有人都阅读的消息就会被删除

ids:chatid
是一个String型的数据,里面放的是最新的消息的编号(发消息时,自增这个字段,即可获得最新的值)

ids:chat:
是一个String型的数据,里面放的是最新的聊天室的编号(创建聊天室时,自增这个字段)

代码

OK 开始看代码 

public String createChat(Jedis conn, String sender, Set<String> recipients, String message) {
//启动的时候redis里是没有ids:chat:这个键的
//自增之后返回1
    String chatId = String.valueOf(conn.incr("ids:chat:"));
    return createChat(conn, sender, recipients, message, chatId);
  } 

  /**
  *
  * @param conn
  * @param sender 发送消息的人
  * @param recipients 接受消息的人
  * @param message 待发送的消息
  * @param chatId 聊天室的编号
  * @return
  */
  public String createChat( Jedis conn, String sender,
    Set<String> recipients, String message, String chatId){
  //自己发的消息 自己也能接受到
    recipients.add(sender); 

    Transaction trans = conn.multi();
    for (String recipient : recipients){
  //聊天室的成员 最开始时 都阅读的是0号信息
      trans.zadd("chat:" + chatId, 0, recipient);
  //记录每个人参加的聊天室
      trans.zadd("seen:" + recipient, 0, chatId);
    }
    trans.exec(); 

    return sendMessage(conn, chatId, sender, message);
  } 

  public String sendMessage(Jedis conn, String chatId, String sender, String message) { 

  //锁住聊天室 为啥? 人员变动了咋办
  //这个acquireLock见上一章
    String identifier = acquireLock(conn, "chat:" + chatId);
    if (identifier == null){
      throw new RuntimeException("Couldn't get the lock");
    }
    try {
    //给要发布的消息设定一个最新的编号 第一次时 返回的是1
      long messageId = conn.incr("ids:" + chatId);
      HashMap<String,Object> values = new HashMap<String,Object>();
      values.put("id", messageId);
      values.put("ts", System.currentTimeMillis());
      values.put("sender", sender);
      values.put("message", message);
      String packed = new Gson().toJson(values); 

      //某个聊天室的消息列表
      //最旧的消息----消息json
      //默认的zset是按照score的值从小到大排序
      conn.zadd("msgs:" + chatId, messageId, packed);
    }finally{
      releaseLock(conn, "chat:" + chatId, identifier);
    }
    return chatId;
  }

 发消息现在就OK了,剩下的就是用户去拉取未读的消息了。这个比较麻烦,恩,相当的麻烦 

 @SuppressWarnings("unchecked")
  public List<ChatMessages> fetchPendingMessages(Jedis conn, String recipient) { 

  //获得用户在各个聊天室 已经看到的最新消息的id
  //有几个聊天室 seenSet的size就是几
    Set<Tuple> seenSet = conn.zrangeWithScores("seen:" + recipient, 0, -1);
    List<Tuple> seenList = new ArrayList<Tuple>(seenSet); 

    Transaction trans = conn.multi();
    for (Tuple tuple : seenList){
      String chatId = tuple.getElement();
      int seenId = (int)tuple.getScore();
      //获取每个聊天室里 未读的所有消息
      //min 和 max 可以是 -inf 和 +inf
      trans.zrangeByScore("msgs:" + chatId, String.valueOf(seenId + 1), "inf");
    }
    //我参加了几个聊天室 results的长度就是几
    List<Object> results = trans.exec(); 

    //com.google.gson.Gson jar包自己下载吧
    Gson gson = new Gson();
    Iterator<Tuple> seenIterator = seenList.iterator();
    Iterator<Object> resultsIterator = results.iterator(); 

    //用户最后成功拉取的未读消息 存放在chatMessages
    List<ChatMessages> chatMessages = new ArrayList<ChatMessages>();
    List<Object[]> seenUpdates = new ArrayList<Object[]>();
    List<Object[]> msgRemoves = new ArrayList<Object[]>(); 

    //这个大的while循环 用户参与了几个聊天室 就循环几次
    while (seenIterator.hasNext()){
      Tuple seen = seenIterator.next();
      Set<String> messageStrings = (Set<String>)resultsIterator.next();
      if (messageStrings.size() == 0){
      //没有未读的消息
        continue;
      } 

      //代码运行到这里
      //说明 我在某个聊天室 还有未读的消息
      //seedid记录我已经拉取到的消息 初始为0
      int seenId = 0;
      //当前处理的是哪个聊天室
      String chatId = seen.getElement(); 

      List<Map<String,Object>> messages = new ArrayList<Map<String,Object>>(); 

      //我在聊天室未读的消息列表
      for (String messageJson : messageStrings){
        Map<String,Object> message = (Map<String,Object>)gson.fromJson(
          messageJson, new TypeToken<Map<String,Object>>(){}.getType());
        int messageId = ((Double)message.get("id")).intValue(); 

        if (messageId > seenId){
          seenId = messageId;
        }
        message.put("id", messageId);
        //加入到成功拉取的列表里
        messages.add(message);
      }
      //更新我在这个聊天室读到的最新消息
      conn.zadd("chat:" + chatId, seenId, recipient); 

      //记录我在某个聊天室读到的最新记录
      seenUpdates.add(new Object[]{"seen:" + recipient, seenId, chatId}); 

      //取出第0个member-score
      Set<Tuple> minIdSet = conn.zrangeWithScores("chat:" + chatId, 0, 0);
      //为啥删除呢? 每个聊天室是一个zset表 第一条记录代表的就是 所有用户至少都读了的消息
      if (minIdSet.size() > 0){
      Tuple tuple=minIdSet.iterator().next();
      System.out.println("要删除的 tuple:"+tuple.getElement()+"--"+tuple.getScore());
        msgRemoves.add(new Object[]{"msgs:" + chatId, tuple.getScore()});
      }
      chatMessages.add(new ChatMessages(chatId, messages));
    } 

    trans = conn.multi();
    for (Object[] seenUpdate : seenUpdates){
      trans.zadd(
        (String)seenUpdate[0],
        (Integer)seenUpdate[1],
        (String)seenUpdate[2]);
    }
    for (Object[] msgRemove : msgRemoves){
      trans.zremrangeByScore(
        (String)msgRemove[0], 0, ((Double)msgRemove[1]).intValue());
    }
    trans.exec(); 

    //返回的是我这次拉取获得的 最新的消息
    return chatMessages;
  }

OK,咱们看看测试代码:

package redisinaction; 

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set; 

import org.junit.BeforeClass;
import org.junit.Test; 

import jedis.redis_in_action.Chapter06;
import jedis.redis_in_action.Chapter06.ChatMessages;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple; 

/**
 * This class is used for ...
 * @author dlf(460795365@qq.com)
 * @version 1.0, 2016年10月17日 下午10:15:58
 */
public class Chapter06Test {
  static Jedis conn = null;
  static Chapter06 c=null; 

  @BeforeClass
  public static void initConn(){
    System.out.println("test before");
    conn = new Jedis("10.150.0.80");
    conn.auth("dlf123123"); 

    c=new Chapter06();
  } 

  @Test
   public void testMultiRecipientMessaging() {
      System.out.println("\n----- testMultiRecipientMessaging -----");
      conn.del("ids:chat:", "msgs:1", "ids:1", "seen:joe", "seen:jeff", "seen:jenny"); 

      System.out.println("Let's create a new chat session with some recipients...");
      Set<String> recipients = new HashSet<String>();
      recipients.add("jeff");
      recipients.add("jenny");
      String chatId = c.createChat(conn, "joe", recipients, "message 1");
      System.out.println("Now let's send a few messages...");
      for (int i = 2; i < 5; i++){
        c.sendMessage(conn, chatId, "joe", "message " + i);
      }
      System.out.println();
      System.out.println("看看消息库");
      //消息库里的所有消息
      Set<Tuple> messageFromBase=conn.zrangeWithScores("msgs:"+chatId, 0, -1);
      Iterator<Tuple> iterator=messageFromBase.iterator();
      while(iterator.hasNext()){
        Tuple tuple=iterator.next();
        System.out.println(tuple.getElement()+" --  "+tuple.getScore());
      }
      System.out.println("And let's get the messages that are waiting for jeff and jenny..."); 

      List<ChatMessages> r1 = c.fetchPendingMessages(conn, "jeff");
      List<ChatMessages> r2 = c.fetchPendingMessages(conn, "jenny");
    //当我拉取了joe的未读信息后 就会删除msgs:1里面的信息
    //为什么?想明白了么?
      List<ChatMessages> r3 = c.fetchPendingMessages(conn, "joe");
      System.out.println("They are the same? " + r1.equals(r2)); 

      System.out.println("Those messages are:");
      for(ChatMessages chat : r1){
        System.out.println(" chatId: " + chat.chatId);
        System.out.println("  messages:");
        for(Map<String,Object> message : chat.messages){
          System.out.println("   " + message);
        }
      } 

      System.out.println("看看还有没");
      messageFromBase=conn.zrangeWithScores("msgs:"+chatId, 0, -1);
       iterator=messageFromBase.iterator();
      while(iterator.hasNext()){
        Tuple tuple=iterator.next();
        System.out.println(tuple.getElement()+" --  "+tuple.getScore());
      }
      conn.del("ids:chat:", "msgs:1", "ids:1", "seen:joe", "seen:jeff", "seen:jenny");
    } 

} 

搞定了,大家不妨把代码复制一份,自己看看
下面的是测试的结果

test before

----- testMultiRecipientMessaging -----
Let's create a new chat session with some recipients...
Now let's send a few messages...

看看消息库
{"sender":"joe","id":1,"message":"message 1","ts":1477276890018} --  1.0
{"sender":"joe","id":2,"message":"message 2","ts":1477276890113} --  2.0
{"sender":"joe","id":3,"message":"message 3","ts":1477276890115} --  3.0
{"sender":"joe","id":4,"message":"message 4","ts":1477276890116} --  4.0
And let's get the messages that are waiting for jeff and jenny...
要删除的 tuple:jenny--0.0
要删除的 tuple:joe--0.0
要删除的 tuple:jeff--4.0
They are the same? true
Those messages are:
  chatId: 1
    messages:
      {sender=joe, id=1, message=message 1, ts=1.477276890018E12}
      {sender=joe, id=2, message=message 2, ts=1.477276890113E12}
      {sender=joe, id=3, message=message 3, ts=1.477276890115E12}
      {sender=joe, id=4, message=message 4, ts=1.477276890116E12}
看看还有没

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索redis
聊天室
php redis实现聊天室、redis实现聊天室、redis实现秒杀功能、redis聊天室、php redis 聊天室,以便于您获取更多的相关知识。

时间: 2024-10-25 12:40:56

Redis实现多人多聊天室功能_Redis的相关文章

Facebook测试聊天室功能:聊天应用大战或激化

新浪科技讯 北京时间6月28日上午消息,据美国科技博客TechCrunch报道,知情人士透露,Facebook已经开发了一个代号为"主办聊天"(Host Chat)的新功能,允许用户设立专属聊天室,而该用户的好友则无需邀请便可加入.Facebook已经证实,该公司已经开始测试这项功能.与AOL的传统聊天室功能类似,这项新功能有望帮助Facebook用户结识好友的好友,并在该网站上停留更长时间.Facebook虽然证实了这项功能,但并未对细节发表评论,只是表示:"我们会时不时地

Facebook业已开发了一项代号为Host Chat的新聊天室功能

Facebook证实,业已开始测试这项功能.与AOL传统聊天室相比,"Host Chat"功能可以让Facebook用户结识好友的好友,并在网站上停留更长时间.尽管Facebook证实正在测试这项功能,但并没有透露更多细节,仅表示:"一小部分用户有时参与这项测试."由于只有一小部分用户参与测试,测试范围很小,有时仅针对特定国家展开,因此除非这项功能测试顺利,并在更大范围内推出,否则其他用户在其自己的Facebook上并看不到这项功能. 消息人士透露该消息正值Face

温馨情缘纯Asp聊天室功能简介

聊天室 温馨情缘纯Asp聊天室功能简介     由软件使用主页独立开发的聊天室程序1.0版本,现以免费的形式发放,任何人皆可以自由使用于任何非商业用途,任何人不可以传播本程序和提供他人下载.凡需要商业用途的需事先告知.软件使用主页保留对本软件的更改与订正权利,任何人不得修改本软件的任何一部分,使用者执行本软件所需的设置或外观美化的更改不在此限.不得撤除包含在软件包中的版权声明.凡非商业用途必须在聊天室的首页加上软件使用主页的连接.连接地址:http://swuse.yeah.net,连接标题:软

ios-在iOS中怎么使用XMPP框架实现聊天室功能呢?谢谢

问题描述 在iOS中怎么使用XMPP框架实现聊天室功能呢?谢谢 使用XMPPFramework在iOS中实现XMPP聊天室功能.现在已经实现点对点聊天.希望得到大牛的帮助,谢谢! 解决方案 上面的答案有帮助吗?如果还有问题,请提出来,如果对答案满意,请顶一下,并标记为采纳答案,谢谢! 解决方案二: 参考开发(六)聊天室" rel="nofollow">XMPPFrameWork IOS 开发(六)聊天室 想要完整的开源项目的话,参考Dollarss 解决方案三: 非常感

web-im是否有聊天室功能?如果没有我是否可以用群组模拟聊天室?

问题描述 我看了文档,安卓和ios都有聊天室的说明,web-im没有,以后是否会支持?现在我是否可以用群组模拟? 解决方案 webim 没有聊天室,群组也是需要用rest接口实现解决方案二:限制webim 支持聊天室功能,可以去下载最新sdk

环信什么时候支持聊天室功能

问题描述 聊天室功能聊天室功能聊天室功能 解决方案 下个版本的sdk会支持聊天室

用PHP+MySQL搭建聊天室功能实例代码_php实例

下面,以一个简单的聊天室设计为例,介绍PHP+MySQL在网页开发中的应用 1.总体设计 1.1 构思与规划: 聊天室的基本原理,就是把每个连上同一网页的用户传送的发言数据储存起来,然后将所有的发言数据传给每一用户.也就是说,用数据库汇集每个人的发言,并将数据库中的数据传给每一个人就实现了聊天室的功能. 1.2 表设计 首先使用MySQL建立表chat用来储存用户的发言: 复制代码 代码如下: mysql> CREATE TABLE chat -> (chtime DATATIME, ->

Asp.net使用SignalR实现聊天室的功能_实用技巧

一.引言 在前一篇文章<Asp.net使用SignalR实现酷炫端对端聊天功能>中,我向大家介绍了如何实现实现端对端聊天的功能的,在这一篇文章中将像大家如何使用SignalR实现群聊这样的功能. 二.实现思路 要想实现群聊的功能,首先我们需要创建一个房间,然后每个在线用户可以加入这个房间里面进行群聊,我们可以为房间设置一个唯一的名字来作为标识.那SignalR类库里面是否有这样现有的方法呢?答案是肯定的. // IGroupManager接口提供如下方法 // 作用:将连接ID加入某个组 //

用PHP和ACCESS写聊天室(一)

INDEX.PHP 代码如下! <?php function delquot($sStr){     $s=str_replace(chr(124),"",$sStr);     $s=str_replace(chr(39),"",$s);     $s=str_replace(chr(34),"",$s);     return $s; } if ($cmdYes=="进   入"){     $sUserName=h