TeamTalk source code analysis-the realization of group chat technical solution and group unread count

Group chat analysis

Insert picture description here

  1. app sends group messages to msg_server
  2. After msg_server receives it, it sets the message creation time with a local timestamp (the client time is unreliable)
  3. Forward
  4. Query the validity of the group ID from the database, ignore it if it is illegal
  5. Whether the member is in the group member or not, ignore it if it is illegal
  6. Whether there is a session between the user and the group, there is no automatically created a session relationship
  7. Generate unique message ID in the group
  8. Write to the database table group_msg (according to groopId%8 points table)
  9. Return ack
  10. Return ack (if there are multiple logins, broadcast msg to multiple ends, and each end will reply ack confirmation)
  11. Query the list of group members from db_proxy_server
  12. Check the database
  13. Receiving group members
  14. Traverse
  15. Direct send online
  16. Offline pushes, first check the group push flag settings.
  17. Check the database
  18. Traverse the list of group push flags of all offline members
  19. If it is not set to do not disturb, push directly. ios: apns, android: various push notifications such as Huawei, Xiaomi or third parties such as Aurora, Homing Pigeon, etc.
  20. Offline users go online
  21. Pull group messages (different from WeChat, the strategy here is: first check the conversation, click on the group to display the latest 20 messages, and then store the local sqlite database. Then scroll up, and continue to pull.)

This solution of TeamTalkd is more suitable for getting started. Adopted the solution of diffuse reading (only one group message is stored and pulled multiple times), and the specific code is not posted.

msg_server mainly looks at the following 2 files:

  1. MsgConn.cpp: _HandleClientMsgData, which handles messages from the client.
  2. DBServConn.cpp: _HandleMsgData, which handles the response from db_proxy_server.

db_proxy_server mainly starts from the message-driven table and finds the implementation of related logic:

  1. HandlerMap.cpp: Message-driven table implementation, according to CmdID to call related processing functions, start from here.
  2. business/GroupMessageModel.cpp: group chat message processing, including storage, unread count update, etc.

Group unread count analysis

The implementation of TeamTalk is mainly divided into 2 parts:

  • The total unread count of the group im_group_msg (1 group, 1 key)
  • Member's unread count im_user_group (1 group with N keys)

The key rules are as follows:

  • The total unread count of the group (in fact, the number of group messages): groupID + _im_group_msg, example: 2_im_group_msg
  • Unread count of group members (more precisely, the number of group messages when the members have been read): userID + _ + groupID + _im_user_group, example: 7_2_im_user_group

The algorithm principle is as follows (assuming a group has 2 members A and B):
The implementation of TeamTalk is mainly divided into 2 blocks:
the total unread count of the group im_group_msg (1 group, 1 key)
, the unread count of members im_user_group (1 group) N Keys)

The key rules are as follows:

  • The total unread count of the group: groupID+_im_group_msg, example: 2_im_group_msg
  • Unread count of group members: userID+_+groupID+_im_user_group, example: 7_2_im_user_group

Algorithm principle (assuming a group has 2 members A and B):
Insert picture description here

  1. When A sends a message, record total=1, A's offset=1, at this time total-A.offset=0, so the number of unread messages of A is 0. B is online at this time, there is no offset of B in redis, then the unread count of B's ​​group message is the total number, which is 1.
  2. When B clicks on the conversation, clearing the unread count is actually the process of updating B.offset. The offset is recorded as the total number of current group messages. The next time you log in, total-B.offset will be 0, that is, it will be cleared.
  3. At this time, A is the same. During the offline period, B sent 4 messages, total=5, total-A.offset=4, and the number of group unread messages during the offline period is calculated.

official:

成员某群未读消息总数 = 群消息总数(im_group_msg) - 群成员已读消息总数(im_user_group)

So why is it designed to use the total number of reads instead of the total number of unreads (for example, if the user is not online, the number of unreads is +1)?

In fact, this can reduce the update frequency of redis. Otherwise, if a group of 200 people sends a message, redis will be updated 199 times. The above design only needs to be updated twice.

Attached example of the structure of redis:
Insert picture description here
message code:

bool CGroupMessageModel::incMessageCount(uint64_t nUserId, uint32_t nGroupId) {
    
    
    bool bRet = false;
    CacheManager *pCacheManager = CacheManager::getInstance();
    CacheConn *pCacheConn = pCacheManager->GetCacheConn("unread");
    if (pCacheConn) {
    
    
        // 2002_im_group_msg
        // |——count: +1
        string strGroupKey = int2string(nGroupId) + GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX;
        pCacheConn->hincrBy(strGroupKey, GROUP_COUNTER_SUBKEY_COUNTER_FIELD, 1);

        // 2002_im_group_msg下所有的key取出来
        map<string, string> mapGroupCount;
        bool bRet = pCacheConn->hgetAll(strGroupKey, mapGroupCount);
        if (bRet) {
    
    
            // 1_2002_im_user_group
            // |——count: 1
            string strUserKey =
                    int2string(nUserId) + "_" + int2string(nGroupId) + GROUP_USER_MSG_COUNTER_REDIS_KEY_SUFFIX;
            string strReply = pCacheConn->hmset(strUserKey, mapGroupCount);
            if (!strReply.empty()) {
    
    
                bRet = true;
            } else {
    
    
                ERROR("hmset %s failed !", strUserKey.c_str());
            }
        } else {
    
    
            ERROR("hgetAll %s failed!", strGroupKey.c_str());
        }
        pCacheManager->RelCacheConn(pCacheConn);
    } else {
    
    
        ERROR("no cache connection for unread");
    }
    return bRet;
}

Clear unread count:

bool CGroupMessageModel::clearMessageCount(uint64_t nUserId, uint32_t nGroupId) {
    
    
    bool bRet = false;
    CacheManager *pCacheManager = CacheManager::getInstance();
    CacheConn *pCacheConn = pCacheManager->GetCacheConn("unread");
    if (pCacheConn) {
    
    
        // 用总数覆盖offset,即total-offset=0
        string strGroupKey = int2string(nGroupId) + GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX;
        map<string, string> mapGroupCount;
        bool bRet = pCacheConn->hgetAll(strGroupKey, mapGroupCount);
        pCacheManager->RelCacheConn(pCacheConn);
        if (bRet) {
    
    
            string strUserKey =
                    int2string(nUserId) + "_" + int2string(nGroupId) + GROUP_USER_MSG_COUNTER_REDIS_KEY_SUFFIX;
            string strReply = pCacheConn->hmset(strUserKey, mapGroupCount);
            if (strReply.empty()) {
    
    
                ERROR("hmset %s failed !", strUserKey.c_str());
            } else {
    
    
                bRet = true;
            }
        } else {
    
    
            ERROR("hgetAll %s failed !", strGroupKey.c_str());
        }
    } else {
    
    
        ERROR("no cache connection for unread");
    }
    return bRet;
}

Get the total number of unread messages of the user:

void CGroupMessageModel::getUnReadCntAll(uint64_t nUserId, uint32_t &nTotalCnt) {
    
    
    list<uint32_t> lsGroupId;
    CGroupModel::getInstance()->getUserGroupIds(nUserId, lsGroupId, 0);
    uint32_t nCount = 0;

    CacheManager *pCacheManager = CacheManager::getInstance();
    CacheConn *pCacheConn = pCacheManager->GetCacheConn("unread");
    if (pCacheConn) {
    
    
        for (auto it = lsGroupId.begin(); it != lsGroupId.end(); ++it) {
    
    
            uint32_t nGroupId = *it;
            string strGroupKey = int2string(nGroupId) + GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX;
            string strGroupCnt = pCacheConn->hget(strGroupKey, GROUP_COUNTER_SUBKEY_COUNTER_FIELD);
            if (strGroupCnt.empty()) {
    
    
//                log("hget %s : count failed !", strGroupKey.c_str());
                continue;
            }
            uint32_t nGroupCnt = (uint32_t) (atoi(strGroupCnt.c_str()));

            string strUserKey =
                    int2string(nUserId) + "_" + int2string(nGroupId) + GROUP_USER_MSG_COUNTER_REDIS_KEY_SUFFIX;
            string strUserCnt = pCacheConn->hget(strUserKey, GROUP_COUNTER_SUBKEY_COUNTER_FIELD);

            uint32_t nUserCnt = (strUserCnt.empty() ? 0 : ((uint32_t) atoi(strUserCnt.c_str())));
            // 这里就是上面说的:total - offset = 未读数量
            if (nGroupCnt >= nUserCnt) {
    
    
                nCount = nGroupCnt - nUserCnt;
            }
            if (nCount > 0) {
    
    
                nTotalCnt += nCount;
            }
        }
        pCacheManager->RelCacheConn(pCacheConn);
    } else {
    
    
        ERROR("no cache connection for unread");
    }
}

About the author

Recommend your own open source IM, written in pure Golang:

CoffeeChat:

https://github.com/xmcy0011/CoffeeChat
opensource im with server(go) and client(flutter+swift)

Referred to well-known projects such as TeamTalk and Guazi IM, including server (go) and client (flutter+swift), single chat and robot (micro, Turing, Sizhi) chat functions have been completed, and group chat functions are currently being developed , Welcome friends Star who are interested in golang and cross-platform development of flutter technology to pay more attention.

————————————————
Copyright statement: This article is the original article of CSDN blogger "Xu Fei", and it follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this for reprinting. statement.
Original link: https://blog.csdn.net/xmcy001122/article/details/109316394

Guess you like

Origin blog.csdn.net/xmcy001122/article/details/109316394