TeamTalkソースコード分析-グループチャットテクニカルソリューションとグループ未読数の実現

グループチャット分析

ここに画像の説明を挿入

  1. アプリはグループメッセージをmsg_serverに送信します
  2. msg_serverはそれを受信すると、メッセージの作成時刻をローカルタイムスタンプで設定します(クライアント時刻は信頼できません)。
  3. フォワード
  4. データベースからグループIDの有効性を照会し、違法な場合は無視します
  5. メンバーがグループメンバーであるかどうかにかかわらず、違法である場合は無視してください
  6. ユーザーとグループの間にセッションがあるかどうかに関係なく、自動的に作成されるセッション関係はありません
  7. グループ内で一意のメッセージIDを生成します
  8. データベーステーブルgroup_msgに書き込みます(groopId%8ポイントテーブルによる)
  9. ackを返す
  10. ackを返します(複数のログインがある場合は、メッセージを複数の端にブロードキャストし、各端がackの確認に応答します)
  11. db_proxy_serverからグループメンバーのリストを照会します
  12. データベースを確認してください
  13. グループメンバーの受け入れ
  14. トラバース
  15. オンラインで直接送信
  16. オフラインプッシュでは、最初にグループプッシュフラグの設定を確認します。
  17. データベースを確認してください
  18. すべてのオフラインメンバーのグループプッシュフラグのリストをトラバースします
  19. 邪魔にならないように設定されていない場合は、直接押してください。ios:apns、android:Huawei、Xiaomiなどのさまざまなプッシュ通知、またはAurora、HomingPigeonなどのサードパーティ。
  20. オフラインユーザーはオンラインになります
  21. グループメッセージをプルします(WeChatとは異なり、ここでの戦略は、最初に会話を確認し、グループをクリックして最新の20メッセージを表示し、次にローカルsqliteデータベースを保存します。次に上にスクロールしてプルを続けます。)

TeamTalkdのこのソリューションは、開始するのにより適しています。拡散読み取りのソリューションを採用し(1つのグループメッセージのみが複数回保存およびプルされる)、特定のコードは投稿されません。

msg_serverは、主に次の2つのファイルを調べます。

  1. MsgConn.cpp:_HandleClientMsgData。クライアントからのメッセージを処理します。
  2. DBServConn.cpp:_HandleMsgData。db_proxy_serverからの応答を処理します。

db_proxy_serverは主にメッセージ駆動型テーブルから開始し、関連するロジックの実装を見つけます。

  1. HandlerMap.cpp:関連する処理関数を呼び出すためのCmdIDによる、メッセージ駆動型テーブルの実装は、ここから開始します。
  2. business / GroupMessageModel.cpp:ストレージ、未読カウントの更新などを含むグループチャットメッセージの処理。

グループ未読カウント分析

TeamTalkの実装は、主に2つの部分に分かれています。

  • グループim_group_msgの未読総数(1グループ、1キー)
  • メンバーの未読カウントim_user_group(Nキーの1グループ)

主なルールは次のとおりです。

  • グループの未読の総数(実際には、グループメッセージの数):groupID + _im_group_msg、例:2_im_group_msg
  • グループメンバーの未読数(より正確には、メンバーが読み取られたときのグループメッセージの数):userID + _ + groupID + _im_user_group、例:7_2_im_user_group

アルゴリズムの原則は次のとおりです(グループに2つのメンバーAとBがあると仮定):
TeamTalkの実装は主に2つのブロックに分割されます:
グループim_group_msg(1グループ、1キー)
の未読総数、メンバーの未読数im_user_group(1グループ)Nキー)

主なルールは次のとおりです。

  • グループの未読の総数:groupID + _im_group_msg、例:2_im_group_msg
  • グループメンバーの未読数:userID + _ + groupID + _im_user_group、例:7_2_im_user_group

アルゴリズムの原則(グループに2人のメンバーAとBがあると仮定):
ここに画像の説明を挿入

  1. Aがメッセージを送信するとき、レコードtotal = 1、Aのoffset = 1、この時点ではtotal-A.offset = 0であるため、Aの未読メッセージの数は0です。この時点でBはオンラインであり、redisにBのオフセットがない場合、Bのグループメッセージの未読数は合計数であり、1です。
  2. Bが会話をクリックすると、未読カウントのクリアは実際にはB.offsetを更新するプロセスです。オフセットは、現在のグループメッセージの総数として記録されます。次にログインすると、total-B.offsetは0になります。つまり、クリアされます。
  3. このとき、Aは同じです。オフライン期間中にBは合計= 5、合計-A.offset = 4の4つのメッセージを送信し、オフライン期間中のグループ内の未読メッセージの数が計算されます。

公式:

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

では、未読の総数ではなく、読み取りの総数を使用するように設計されているのはなぜですか(たとえば、ユーザーがオンラインでない場合、未読の数は+1になります)。

実際、これによりredisの更新頻度を減らすことができます。そうでない場合、200人のグループがメッセージを送信すると、redisは199回更新されます。上記のデザインは2回更新するだけで済みます。

redisの構造の添付例:
ここに画像の説明を挿入
メッセージコード:

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;
}

未読カウントをクリアする:

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;
}

ユーザーの未読メッセージの総数を取得します。

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");
    }
}

著者について

純粋なGolangで書かれた独自のオープンソースIMをお勧めします。

CoffeeChat:

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

サーバー(go)とクライアント(flutter + swift)、シングルチャットとロボット(micro、Turing、Sizhi)のチャット機能を含む、TeamTalkやGuazi IMなどの有名なプロジェクトが完了し、現在グループチャット機能が実行されています。開発された、より多くの注意を払うためにフラッターテクノロジーのgolangとクロスプラットフォーム開発に興味を持っているWelcomefriendsStar。

————————————————
著作権表示:この記事はCSDNブロガー「XuFei」のオリジナル記事であり、CC 4.0BY-SA著作権表示に準拠しています。元のソースを添付してくださいリンクとこれを再印刷します。ステートメント。
元のリンク:https://blog.csdn.net/xmcy001122/article/details/109316394

おすすめ

転載: blog.csdn.net/xmcy001122/article/details/109316394