Redis实战之多用户聊天室

多客户端之间的通信,可采用消息推送和消息拉取两种方法。所谓消息推送,即采用publish/subscribe模式,但该方法要求用户必须在线才能接收到消息;所谓消息拉取模式,即由接收端自己拉取存储在某种邮箱里的消息。

数据结构

1、群

群ID计数器:(string)

ids:chat

群成员:(zset,包括群内所有成员及其拉取的最新消息ID)

chat:群ID Last_Message_ID 用户ID

消息ID计数器:(string)

ids:msg:群ID

群消息:(zset)

msgs:群ID 消息ID 消息内容

2、用户

群组:(zset,包括用户加入的群及从群内拉取的最新消息ID)

seen:用户ID Last_Message_ID 群ID

原理

1、建群

incr ids:chat

2、进群

2.1、获取最新群消息ID:

Current_Message_ID = get ids:msg:群ID

2.2、修改群成员集合,更新用户从群内拉取的最新消息ID:

zadd chat:群ID Current_Message_ID 用户ID

2.3、修改用户加入的群,更新用户从群内拉取的最新消息ID:

zadd seen:用户ID Current_Message_ID 群ID

3、退群

3.1、修改群成员集合:

zrem chat:群ID 用户ID

3.2、修改用户加入的群:

zrem seen:用户ID 群ID

3.3、清除群消息:
1)获取群成员集合中剩余成员数目:

Number = zcard chat:群ID
2)若 Number <= 0,则删除群消息ID计数器和群内所有消息:
del ids:msg:群ID
zremrangebyrank msgs:群ID 0 -1
3)若 Number > 0,则:
获取群成员集合中最小的score,最小score之前的消息必定已被所有用户拉取:
Min_Score = zrange chat:群ID 0 0 withscores
清除已被所有成员拉取的消息:
zremrangebyscore msgs:群ID 0 Min_Score

4、发送消息

4.1、创建群消息ID:

New_Message_ID = incr ids:msg:群ID

4.2、将消息添加到群消息集合:

zadd msgs:群ID New_Message_ID 消息内容

5、拉取消息

5.1、获取用户所加入的群信息(包括群ID和从群内最后拉取的消息ID):

<群ID, Last_Message_ID> = zrange seen:用户ID 0 -1 withscores

5.2、遍历所加入的群,拉取所有群消息(并记录最大未读群消息ID:Max_Message_ID):

zrangebyscore msgs:群ID Last_Message_ID+1 inf

5.3、修改群成员集合和用户加入群集合,更新用户从群内拉取的最大消息ID:

zadd chat:群ID Max_Message_ID 用户ID
zadd seen:用户ID Max_Message_ID 群ID

5.4、清除已被所有成员拉取的消息:
获取群成员集合中最小的score,最小score之前的消息必定已被所有用户拉取:

Min_Score = zrange chat:群ID 0 0 withscores
清除已被所有人阅读的消息:
zremrangebyscore msgs:群ID -inf Min_Score

实现

1、建群

int ChatManager::createChat()
{
    // 分配群ID
    redisReply *pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "INCR ids:chat");
    if (pRedisReply == NULL)
    {
        return -1;
    }

    int nChatID = pRedisReply->integer;
    freeReplyObject(pRedisReply);

    return nChatID;
}

2、进群

bool ChatManager::joinChat(int nChatID, QString &strUser)
{
    //1、获取最新群消息ID
    QString strMsgIDKeyName = QString("ids:msg:%1").arg(nChatID);
    redisReply *pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "GET %s", strMsgIDKeyName.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    int nLastMsgID = pRedisReply->integer;
    freeReplyObject(pRedisReply);

    //2、修改群成员集合,更新用户从群内拉取的最新消息ID
    QString strChatKeyName = QString("chat:%1").arg(nChatID);
    pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZADD %s %d %s", strChatKeyName.toLocal8Bit().data(), nLastMsgID, strUser.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    freeReplyObject(pRedisReply);

    //3、修改用户加入的群,更新用户从群内拉取的最新消息ID
    QString strSeenKeyName = QString("seen:%1").arg(strUser);
    pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZADD %s %d %d", strSeenKeyName.toLocal8Bit().data(), nLastMsgID, nChatID);
    if (pRedisReply == NULL)
    {
        return false;
    }

    freeReplyObject(pRedisReply);

    return true;
}

3、退群

bool ChatManager::leaveChat(int nChatID, QString &strUser)
{
    //1、修改群成员集合
    QString strChatKeyName = QString("chat:%1").arg(nChatID);
    redisReply *pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZREM %s %s", strChatKeyName.toLocal8Bit().data(), strUser.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    freeReplyObject(pRedisReply);

    //2、修改用户加入的群
    QString strSeenKeyName = QString("seen:%1").arg(strUser);
    pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZREM %s %d", strSeenKeyName.toLocal8Bit().data(), nChatID);
    if (pRedisReply == NULL)
    {
        return false;
    }

    freeReplyObject(pRedisReply);

    //清除群消息
    //3、获取群成员集合中剩余成员数目
    pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZCARD %s", strChatKeyName.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    int nMembers = pRedisReply->integer;
    freeReplyObject(pRedisReply);

    if (nMembers <= 0)
    {
        //群内无剩余成员
        //4、删除群消息ID计数器
        QString strMsgIDKeyName = QString("ids:msg:%1").arg(nChatID);
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "DEL %s", strMsgIDKeyName.toLocal8Bit().data());
        if (pRedisReply == NULL)
        {
            return false;
        }

        freeReplyObject(pRedisReply);

        //5、删除群内所有消息
        QString strMsgKeyName = QString("msg:%1").arg(nChatID);
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZREMRANGEBYRANK %s 0 -1", strMsgKeyName.toLocal8Bit().data());
        if (pRedisReply == NULL)
        {
            return false;
        }

        freeReplyObject(pRedisReply);
    }
    else
    {
        //群内还有剩余成员 清除已被所有成员拉取的消息
        //6、获取群成员集合中最小的score,最小score之前的消息必定已被所有用户拉取
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZRANGE %s 0 0 WITHSCORES", strChatKeyName.toLocal8Bit().data());
        if (pRedisReply == NULL)
        {
            return false;
        }

        int nScore = atoi(pRedisReply->element[1]->str);
        freeReplyObject(pRedisReply);

        //7、清除已被所有成员拉取的消息
        QString strMsgKeyName = QString("msg:%1").arg(nChatID);
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZREMRANGEBYSCORE %s 0 %d", strMsgKeyName.toLocal8Bit().data(), nScore);
        if (pRedisReply == NULL)
        {
            return false;
        }

        freeReplyObject(pRedisReply);
    }

    return true;
}

4、发送消息

bool ChatManager::sendMsg(int nChatID, QString &strMsgInfo)
{
    //1、创建群消息ID
    QString strMsgIDKeyName = QString("ids:msg:%1").arg(nChatID);
    redisReply *pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "INCR %s", strMsgIDKeyName.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    int nNewMsgID = pRedisReply->integer;
    freeReplyObject(pRedisReply);

    //2、将消息添加到群消息集合
    QString strMsgKeyName = QString("msg:%1").arg(nChatID);
    pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZADD %s %d %s", strMsgKeyName.toLocal8Bit().data(), nNewMsgID, strMsgInfo.toLocal8Bit().data());
    if (pRedisReply == NULL)
    {
        return false;
    }

    freeReplyObject(pRedisReply);

    return true;
}

5、拉取消息

bool ChatManager::fetchMsg(QString &strUser)
{
    //1、获取用户所加入的群信息(包括群ID和从群内最后拉取的消息ID)
    QString strSeenKeyName = QString("seen:%1").arg(strUser);
    redisReply *pSeenRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZRANGE %s 0 -1 WITHSCORES", strSeenKeyName.toLocal8Bit().data());
    if (pSeenRedisReply == NULL)
    {
        return false;
    }

    //2、遍历所加入的群
    for (int nIndex = 0; nIndex < pSeenRedisReply->elements; nIndex = nIndex + 2)
    {
        int nChatID = atoi(pSeenRedisReply->element[nIndex]->str);
        int nNextLastMsgID = atoi(pSeenRedisReply->element[nIndex + 1]->str) + 1;

        //3、拉取所有群消息
        QString strMsgKeyName = QString("msg:%1").arg(nChatID);
        redisReply *pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZRANGEBYSCORE %s %d INF WITHSCORES", strMsgKeyName.toLocal8Bit().data(), nNextLastMsgID);
        if (pRedisReply == NULL)
        {
            freeReplyObject(pSeenRedisReply);
            return false;
        }

        if (0 == pRedisReply->elements)
        {
            continue;   //没有未读消息
        }

        //4、展示所有未读消息
        int nMaxMsgID = 0;
        for (int nMsgIndex = 0; nMsgIndex < pRedisReply->elements; nMsgIndex = nMsgIndex + 2)
        {
            QString strMsgInfo = pRedisReply->element[nMsgIndex]->str;
            int nMsgID = atoi(pRedisReply->element[nMsgIndex + 1]->str);

            // 展示消息

            if (nMsgID > nMaxMsgID)
            {
                nMaxMsgID = nMsgID;
            }
        }

        freeReplyObject(pRedisReply);

        //5、修改群成员集合 更新用户从群内拉取的最大消息ID
        QString strChatKeyName = QString("chat:%1").arg(nChatID);
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZADD %s %d %s", strChatKeyName.toLocal8Bit().data(), nMaxMsgID, strUser.toLocal8Bit().data());
        if (pRedisReply == NULL)
        {
            freeReplyObject(pSeenRedisReply);
            return false;
        }

        freeReplyObject(pRedisReply);

        //6、修改用户加入群集合 更新用户从群内拉取的最大消息ID
        QString strSeenKeyName = QString("seen:%1").arg(strUser);
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZADD %s %d %d", strSeenKeyName.toLocal8Bit().data(), nMaxMsgID, nChatID);
        if (pRedisReply == NULL)
        {
            freeReplyObject(pSeenRedisReply);
            return false;
        }

        freeReplyObject(pRedisReply);

        //清除已被所有成员拉取的消息
        //7、获取群成员集合中最小的score,最小score之前的消息必定已被所有用户拉取
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZRANGE %s 0 0 WITHSCORES", strChatKeyName.toLocal8Bit().data());
        if (pRedisReply == NULL)
        {
            freeReplyObject(pSeenRedisReply);
            return false;
        }

        if (pRedisReply->elements != 2)
        {
            continue;
        }

        int nOldMsgID = atoi(pRedisReply->element[1]->str);
        freeReplyObject(pRedisReply);

        //8、清除已被所有人阅读的消息
        pRedisReply = (redisReply *)redisCommand(m_pRedisContext, "ZREMRANGEBYSCORE %s -INF %d", strMsgKeyName.toLocal8Bit().data(), nOldMsgID);
        if (pRedisReply == NULL)
        {
            freeReplyObject(pSeenRedisReply);
            return false;
        }

        freeReplyObject(pRedisReply);
    }

    freeReplyObject(pSeenRedisReply);

    return true;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值