TeamTalk知识点梳理一(单聊)

文章目录

  • db_proxy_server
    • db_proxy_server reactor响应处理流程
    • 连接池
      • redis连接池
      • MySQL连接池
  • 单聊消息
    • 消息如何封装?如何保证对端完整解析一帧消息?协议格式?
    • 单聊消息流转流程
    • 消息序号(msg_id )为什么使用redis生成?
  • 未读消息计数
    • 未读消息计数之单聊
    • 未读消息计数之群聊
  • 清除未读消息计数

在这里插入图片描述

db_proxy_server

db_proxy_server数据库代理服务。

db_proxy_server reactor响应处理流程

  1. 数据入口 reactor CProxyConn:: HandlePduBuf
  2. 怎么初始化epoll+线程池
  3. 任务封装
  4. 把任务放入线程池
  5. 执行任务
  6. 把要回应的数据放入回复列表CProxyConn::SendResponsePdulist
  7. epoll所在线程读取回复列表的数据发给请求端

连接池

  1. 为什么使用连接池?
    答:对象复用,减小频繁创建链接释放链接的开销时间。
  2. 为什么分开不同的db redis?
    答:方便扩展。
  3. pool_name的意义?
    答:抽象,不必关注redis是否分布式。

redis连接池

  1. CacheInstances=unread,group_set,token,sync,group_member 5 个连接池

  2. 具体设计逻辑?
    答:设计了redis连接池。具体工作细节简要概括如下:

  3. 第一步我们先创建redis连接池类(CachePool类),该类的私有成员变量主要有存放redis的连接list容器;每个redis连接池的对象的名字(m_pool_name);该类的方法主要有获取和释放redis连接,获取连接池的对象的名字等。

  4. 第二步我们创建CacheManager类用来管理redis连接池对象。该类的私有成员变量主要有map容器在将连接池对象的pool_name和连接池对象映射起来。db_proxy_server的main函数首先初始化CacheManager类的单例对象(pCacheManager)。主要逻辑是读取配置文件来创建不同的redis连接池,不同的连接池有不同的名字,创建连接池对象后,放到一个map容器里管理起来。连接池主要包括(unread group_member等)我们后续的相关业务逻辑主要实现了unread 连接池,里面包括主要包括单聊和群聊的未读消息计数。

  5. unread的业务逻辑展开:数据库代理模块之redis

MySQL连接池

答:设计了MySQL连接池。

  1. 第一步我们先创建MySQL连接池类(CDBPool类),该类的私有成员变量主要有存放MySQL的连接list容器;每个MySQL连接池的对象的名字(m_pool_name);
  2. 第二步我们创建CDBManager类用来管理MySQL连接池对象。该类的私有成员变量主要用map容器将MySQL连接池对象的名字pool_name和MySQL连接池对象映射起来。在db_proxy_server的main函数初始化CDBManager类的单例对象。主要逻辑是读取配置文件来创建不同的MySQL连接池(主库master和从库slave),创建连接池对象后,insert map容器里管理起来。
    线程池
    设计初衷:db_proxy_server的主线程(IO线程)用来进行网络IO(收发数据包以及解包逻辑,耗时的查询数据库等操作会包装成task对象,投递到工作线程池里,由线程池的工作线程进行处理)。
    具体实现:
    CWorkerThread类私有成员变量包含互斥锁,条件变量,线程idx,list<CTask*> m_task_list;对外提供接口有start()用于启动一个线程;Execute()执行任务task。
void CWorkerThread::Execute()
{while (true) {m_thread_notify.Lock();// put wait in while cause there can be spurious wake up (due to signal/ENITR)while (m_task_list.empty()) {m_thread_notify.Wait();}CTask* pTask = m_task_list.front();m_task_list.pop_front();m_thread_notify.Unlock();pTask->run();delete pTask;m_task_cnt++;//log("%d have the execute %d task\n", m_thread_idx, m_task_cnt);}
}

我们使用的是CThreadPool类对象,CThreadPool类私有成员变量包含线程个数以及工作线程数组。对外接口提供初始化(根据入参工作线程个数动态创建工作线程数组)和销毁工作线程数组操作;接收task(往工作线程池里投递任务task),现在是随机选取一个工作线程的idx来进行投递,其实更好的应该是做负载均衡。

void CThreadPool::AddTask(CTask* pTask)
{/** select a random thread to push task* we can also select a thread that has less task to do* but that will scan the whole thread list and use thread lock to get each task size*/uint32_t thread_idx = random() % m_worker_size;m_worker_list[thread_idx].PushTask(pTask);
}void CWorkerThread::PushTask(CTask* pTask)
{m_thread_notify.Lock();m_task_list.push_back(pTask);m_thread_notify.Signal();m_thread_notify.Unlock();
}

单聊消息

消息如何封装?如何保证对端完整解析一帧消息?协议格式?

  1. 答:消息封装采用包头(Header)+包体(Body)的格式。包头自定义格式如下代码所示,包体采用protobuf序列化。
typedef struct {uint32_t    length;        // the whole pdu lengthuint16_t    version;       // pdu version numberuint16_t    flag;          // not useduint16_t    service_id;    //uint16_t    command_id;    //uint16_t    seq_num;       // 包序号uint16_t    reversed;      // 保留
} PduHeader_t;
  1. 如何保证对端完整解析一帧消息?
  • 采用tcp保证数据传输可靠性
  • 通过包头的 length 字段标记一帧消息的长度
  • 通过service id 和 command id区分不同的命令(比如登录、退出等)
  • 解决数据TCP粘包(包头长度字段)、半包(放入网络库的缓冲区)问题
void CImConn::OnRead()
{for (;;){uint32_t free_buf_len = m_in_buf.GetAllocSize() - m_in_buf.GetWriteOffset();if (free_buf_len < READ_BUF_SIZE)m_in_buf.Extend(READ_BUF_SIZE);int ret = netlib_recv(m_handle, m_in_buf.GetBuffer() + m_in_buf.GetWriteOffset(), READ_BUF_SIZE);if (ret <= 0)break;m_recv_bytes += ret;m_in_buf.IncWriteOffset(ret);m_last_recv_tick = get_tick_count();}CImPdu* pPdu = NULL;try{while ( ( pPdu = CImPdu::ReadPdu(m_in_buf.GetBuffer(), m_in_buf.GetWriteOffset()) ) ){uint32_t pdu_len = pPdu->GetLength();HandlePdu(pPdu);m_in_buf.Read(NULL, pdu_len);delete pPdu;pPdu = NULL;
//                        ++g_recv_pkt_cnt;}} catch (CPduException& ex) {log("!!!catch exception, sid=%u, cid=%u, err_code=%u, err_msg=%s, close the connection ",ex.GetServiceId(), ex.GetCommandId(), ex.GetErrorCode(), ex.GetErrorMsg());if (pPdu) {delete pPdu;pPdu = NULL;}OnClose();}
}
  1. 协议格式
message IMMsgData{//cmd id:                0x0301required uint32 from_user_id = 1;    //消息发送方required uint32 to_session_id = 2;    //消息接受方required uint32 msg_id = 3;required uint32 create_time = 4; required IM.BaseDefine.MsgType msg_type = 5;required bytes msg_data = 6;optional bytes attach_data = 20;
}message IMMsgDataAck{//cmd id:                0x0302required uint32 user_id = 1;    //发送此信令的用户idrequired uint32 session_id = 2;                                required uint32 msg_id = 3;required IM.BaseDefine.SessionType session_type = 4;
}

单聊消息流转流程

db_proxy_server示意图:
在这里插入图片描述

答:两个用户A给B发消息,用户A把聊天消息封装好以后发送给MsgServer;同时把消息进行持久化,将聊天消息发给这个 DBProxy(数据库代理服务),存储消息成功后,DBProxyServer组包应答MsgServer,MsgServer收到回复后组包应答Client A。如果 A 和 B 两个用户不在同一个 MsgServer 上,那么会通过这个 RouteServer 去中转Pdu包数据(广播给所有的MsgServer,MsgServer再广播给Client B),B收到消息后应答MsgServer,至此,流程结束。然后如果是一些热点数据(未读消息计数;msg_id计数),我们同时也会写Redis。

消息序号(msg_id )为什么使用redis生成?

  1. 消息ID(msg_id )的作用是防止消息乱序。
  2. 消息ID(msg_id )为什么这么设计?
    答:msg_id 存储在 unread 连接池所在的redis数据库。单聊 msg_id 的 key涉及到 nRelateId。nRelateId 从关系表(IMRelationShip :两个用户id的映射关系)中获取。
/***  获取会话关系ID*  对于群组,必须把nUserBId设置为群ID**  @param nUserAId  <#nUserAId description#>*  @param nUserBId  <#nUserBId description#>*  @param bAdd      <#bAdd description#>*  @param nStatus 0 获取未被删除会话,1获取所有。*/
uint32_t CRelationModel::getRelationId(uint32_t nUserAId, uint32_t nUserBId, bool bAdd)
{uint32_t nRelationId = INVALID_VALUE;if (nUserAId == 0 || nUserBId == 0) {log("invalied user id:%u->%u", nUserAId, nUserBId);return nRelationId;}CDBManager* pDBManager = CDBManager::getInstance();CDBConn* pDBConn = pDBManager->GetDBConn("teamtalk_slave");if (pDBConn){uint32_t nBigId = nUserAId > nUserBId ? nUserAId : nUserBId;uint32_t nSmallId = nUserAId > nUserBId ? nUserBId : nUserAId;string strSql = "select id from IMRelationShip where smallId=" + int2string(nSmallId) + " and bigId="+ int2string(nBigId) + " and status = 0";CResultSet* pResultSet = pDBConn->ExecuteQuery(strSql.c_str());if (pResultSet){while (pResultSet->Next()){nRelationId = pResultSet->GetInt("id");}delete pResultSet;}else{log("there is no result for sql:%s", strSql.c_str());}pDBManager->RelDBConn(pDBConn);if (nRelationId == INVALID_VALUE && bAdd){nRelationId = addRelation(nSmallId, nBigId);}}else{log("no db connection for teamtalk_slave");}return nRelationId;
}
3. 群聊和单聊msg_id 的区别:key设置不同。
uint32_t CMessageModel::getMsgId(uint32_t nRelateId)
{uint32_t nMsgId = 0;CacheManager* pCacheManager = CacheManager::getInstance();CacheConn* pCacheConn = pCacheManager->GetCacheConn("unread");if(pCacheConn){string strKey = "msg_id_" + int2string(nRelateId);nMsgId = pCacheConn->incrBy(strKey, 1);pCacheManager->RelCacheConn(pCacheConn);}return nMsgId;
}/***  获取一个群组的msgId,自增,通过redis控制*  @param nGroupId 群Id*  @return 返回msgId*/
uint32_t CGroupMessageModel::getMsgId(uint32_t nGroupId)
{uint32_t nMsgId = 0;CacheManager* pCacheManager = CacheManager::getInstance();CacheConn* pCacheConn = pCacheManager->GetCacheConn("unread");if(pCacheConn){// TODOstring strKey = "group_msg_id_" + int2string(nGroupId);nMsgId = pCacheConn->incrBy(strKey, 1);pCacheManager->RelCacheConn(pCacheConn);}else{log("no cache connection for unread");}return nMsgId;
}

未读消息计数

未读消息计数

  • 未读消息为什么不可以用MySQL?(提高效率)

未读消息计数之单聊

key设计:“unread_ + int2string(nUserld)”
使用一个hash存储同一个user_id对应不同聊天的未读消息数量
暂时无法在飞书文档外展示此内容
发送消息时,写入数据库后;增加对方未读消息计数调用 incMsgCount 函数接口。

void CMessageModel::incMsgCount(uint32_t nFromId, uint32_t nToId)
{CacheManager* pCacheManager = CacheManager::getInstance();// increase message countCacheConn* pCacheConn = pCacheManager->GetCacheConn("unread");if (pCacheConn) {pCacheConn->hincrBy("unread_" + int2string(nToId), int2string(nFromId), 1);pCacheManager->RelCacheConn(pCacheConn);} else {log("no cache connection to increase unread count: %d->%d", nFromId, nToId);}
}

未读消息计数之群聊

思考:一千人的大群,1个人发信息,其他人都不在线,给每个群成员做未读计数吗?
群聊的未读消息机制如果和单聊做同样的设计?即收到群聊后对每个群成员聊天数量+1?答:效率低下。
方案如下:

  1. 一个群groud_id对应多个user_id
  2. 同一个groud_id,不同的user_id对应的未读消息数量是不一样的
  3. 每次发消息时群消息数量+1,发消息的个人计数也+1
  4. 未读消息数量:群消息数量 - 个人已读消息数量
    对于群组未读消息,每个人都是不一样的,所以设计如下:
    key为"nGroupld_ + nUserld"结合。
#define GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX    "_im_group_msg"
#define GROUP_USER_MSG_COUNTER_REDIS_KEY_SUFFIX     "_im_user_group"
#define GROUP_COUNTER_SUBKEY_COUNTER_FIELD          "count"

作为消息发送者,首先增加群消息总共的消息数量,同时在发送群聊的时候也要把自己的消息计数设置成和群消息数量一样(即没有未读计数)。

/***  增加群消息计数**  @param nUserId  用户Id*  @param nGroupId 群组Id**  @return 成功返回true,失败返回false*/
bool CGroupMessageModel::incMessageCount(uint32_t nUserId, uint32_t nGroupId)
{bool bRet = false;CacheManager* pCacheManager = CacheManager::getInstance();CacheConn* pCacheConn = pCacheManager->GetCacheConn("unread");if (pCacheConn){string strGroupKey = int2string(nGroupId) + GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX;pCacheConn->hincrBy(strGroupKey, GROUP_COUNTER_SUBKEY_COUNTER_FIELD, 1);map<string, string> mapGroupCount;bool bRet = pCacheConn->hgetAll(strGroupKey, mapGroupCount);if(bRet){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{log("hmset %s failed !", strUserKey.c_str());}}else{log("hgetAll %s failed!", strGroupKey.c_str());}pCacheManager->RelCacheConn(pCacheConn);}else{log("no cache connection for unread");}return bRet;
}

清除未读消息计数

CID_MSG_READ_ACK 代表我们已经阅读了消息
CID_MSG_DATA ACK 代表收到消息
单聊和群聊:
客户端读取消息后要发送CID_MSG_READ_ACK给服务器,服务器根据该命令清除未读消息计数。

  • 单聊:直接删除"unread_nUserId" 的key
  • 群聊:更新 “user_id+group_id+_im_user_group”(自己已经读取的消息数量) 对应的value和"group_id+_im_group_msg"(群总共的消息数量)一致
m_handler_map.insert(make_pair(uint32_t(CID_MSG_READ_ACK), DB_PROXY::clearUnreadMsgCounter));void clearUnreadMsgCounter(CImPdu* pPdu, uint32_t conn_uuid){IM::Message::IMMsgDataReadAck msg;if(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength())){uint32_t nUserId = msg.user_id();uint32_t nFromId = msg.session_id();IM::BaseDefine::SessionType nSessionType = msg.session_type();CUserModel::getInstance()->clearUserCounter(nUserId, nFromId, nSessionType);log("userId=%u, peerId=%u, type=%u", nFromId, nUserId, nSessionType);}else{log("parse pb failed");}}
void CUserModel::clearUserCounter(uint32_t nUserId, uint32_t nPeerId, IM::BaseDefine::SessionType nSessionType)
{if(IM::BaseDefine::SessionType_IsValid(nSessionType)){CacheManager* pCacheManager = CacheManager::getInstance();CacheConn* pCacheConn = pCacheManager->GetCacheConn("unread");if (pCacheConn){// Clear P2P msg Counterif(nSessionType == IM::BaseDefine::SESSION_TYPE_SINGLE){int nRet = pCacheConn->hdel("unread_" + int2string(nUserId), int2string(nPeerId));if(!nRet){log("hdel failed %d->%d", nPeerId, nUserId);}}// Clear Group msg Counterelse if(nSessionType == IM::BaseDefine::SESSION_TYPE_GROUP){string strGroupKey = int2string(nPeerId) + GROUP_TOTAL_MSG_COUNTER_REDIS_KEY_SUFFIX;map<string, string> mapGroupCount;bool bRet = pCacheConn->hgetAll(strGroupKey, mapGroupCount);if(bRet){string strUserKey = int2string(nUserId) + "_" + int2string(nPeerId) + GROUP_USER_MSG_COUNTER_REDIS_KEY_SUFFIX;string strReply = pCacheConn->hmset(strUserKey, mapGroupCount);if(strReply.empty()) {log("hmset %s failed !", strUserKey.c_str());}}else{log("hgetall %s failed!", strGroupKey.c_str());}}pCacheManager->RelCacheConn(pCacheConn);}else{log("no cache connection for unread");}}else{log("invalid sessionType. userId=%u, fromId=%u, sessionType=%u", nUserId, nPeerId, nSessionType);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/468619.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLaMA-Factory学习笔记(1)——采用LORA对大模型进行SFT并采用vLLM部署的全流程

该博客是我根据自己学习过程中的思考与总结来写作的&#xff0c;由于初次学习&#xff0c;可能会有错误或者不足的地方&#xff0c;望批评与指正。 1. 安装 1.1 LLaMA-Factory安装 安装可以参考官方 readme &#xff08;https://github.com/hiyouga/LLaMA-Factory/blob/main/…

Linux -- 进程初印象

目录 预备知识 切入点 PCB 看见进程 pid getpid 函数 预备知识 Linux -- 冯诺依曼体系结构&#xff08;硬件&#xff09;-CSDN博客https://blog.csdn.net/2301_76973016/article/details/143598784?spm1001.2014.3001.5501 Linux -- 操作系统&#xff08;软件&#xf…

342--358作业整理(错误 + 重点)

目录 1. 在需要运行的类中 定义 main 方法 2. this 。访问逻辑&#xff1a;先访问本类中&#xff0c;再访问父类中可以访问的成员&#xff08;不包括和本类中重名的成员&#xff09; 3. super 。访问逻辑&#xff1a;super&#xff08;父类对象&#xff09;直接访问父类及以…

Jekins篇(搭建/安装/配置)

目录 一、环境准备 1. Jenkins安装和持续集成环境配置 2. 服务器列表 3. 安装环境 Jekins 环境 4. JDK 环境 5. Maven环境 6. Git环境 方法一&#xff1a;yum安装 二、JenKins 安装 1. JenKins 访问 2. jenkins 初始化配置 三、Jenkins 配置 1. 镜像配置 四、Mave…

【Linux】冯诺依曼体系结构

目录 一、冯诺依曼体系结构二、冯诺依曼体系结构的基本组成三、关于冯诺依曼体系结构的一些问题结尾 一、冯诺依曼体系结构 冯诺依曼体系结构&#xff0c;也称为普林斯顿结构&#xff0c;是现代计算机设计的基础框架。这一体系结构由数学家冯诺依曼在20世纪40年代提出&#xf…

M1M2 MAC安装windows11 虚拟机的全过程

M1/M2 MAC安装windows11 虚拟机的全过程 这两天折腾了一下windows11 arm架构的虚拟机&#xff0c;将途中遇到的坑总结一下。 1、虚拟机软件&#xff1a;vmware fusion 13.6 或者 parallel 19 &#xff1f; 结论是&#xff1a;用parellel 19。 这两个软件都安装过&#xff0…

NAT、代理服务与内网穿透技术全解析

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 NAT 技术背景NAT IP 转换过程NAPTNAT 技术的缺陷 代理服务器正向代理工作原理功能特点应用场景 反向代理基本原理应用场景 NAT 和代理…

优选算法 - 1 ( 双指针 移动窗口 8000 字详解 )

一&#xff1a;双指针 1.1 移动零 题目链接&#xff1a;283.移动零 class Solution {public void moveZeroes(int[] nums) {for(int cur 0, dest -1 ; cur < nums.length ; cur){if(nums[cur] 0){}else{dest; // dest 先向后移动⼀位int tmp nums[cur];nums[cur] num…

qt配合映美精取图开发

最近开发一个项目&#xff0c;用映美精相机配合halcon做取图开发&#xff0c;由于网上资料小特意写个记录。到映美精官网下载驱动&#xff0c;映美精官网&#xff0c;下载映美精的工具开发包SDK 映美精的SDK下载SDK后找到classlib文件夹 里面就是SDK新建一个qt程序&#xff0c…

华为云计算HCIE-Cloud Computing V3.0试验考试北京考场经验分享

北京试验考场 北京考场位置 1.试验考场地址 北京市海淀区北清路156号中关村环保科技示范园区M地块Q21楼 考试场选择北京&#xff0c;就是上面这个地址&#xff0c;在预约考试的时候会显示地址&#xff0c;另外在临近考试的时候也会给你发邮件&#xff0c;邮件内会提示你考试…

LeetCode 509.斐波那契数

动态规划思想 五步骤&#xff1a; 1.确定dp[i]含义 2.递推公式 3.初始化 4.遍历顺序 5.打印dp数组 利用状态压缩&#xff0c;简化空间复杂度。在原代码中&#xff0c;dp 数组保存了所有状态&#xff0c;但实际上斐波那契数列的计算只需要前两个状态。因此&#xff0c;我们…

反向代理开发

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

RabbitMQ — 异步调用

RabbitMQ 是一个开源的消息代理中间件&#xff0c;它使用高级消息队列协议&#xff08;AMQP, Advanced Message Queuing Protocol&#xff09;来实现不同系统之间的消息传递。它以 Erlang 语言编写&#xff0c;具有高可靠性、灵活性和易于扩展的特点&#xff0c;被广泛应用于异…

2025 年使用 Python 和 Go 解决 Cloudflare 问题

作为一名从事网络自动化和爬取工作的开发者&#xff0c;我亲眼目睹了日益复杂的安全性措施带来的挑战。其中一项挑战是 Cloudflare 的 Turnstile CAPTCHA 系统&#xff0c;目前该系统已在全球 2600 多万个网站上使用。这种先进的解决方案重新定义了我们对机器人检测的处理方式&…

大数据的实时处理:工具和最佳实践

在当今的数字世界中&#xff0c;数据以前所未有的速度从无数来源生成&#xff0c;包括社交媒体、物联网设备、电子商务平台等。随着组织认识到这些数据的潜在价值&#xff0c;他们越来越多地转向实时处理&#xff0c;以获得即时、可操作的见解。但是&#xff0c;实时处理大数据…

104、Python并发编程:基于事件Event实现多线程间的同步

引言 继续介绍关于多线程同步的实现方式&#xff0c;本文将介绍基于Event的线程同步方式。 本文的主要内容有&#xff1a; 1、什么是Event 2、Event的使用场景 3、Event的代码实例 4、Event与Condition的比较 什么是Event 在Python的多线程编程中&#xff0c;Event是一个…

第2章2.3立项【硬件产品立项的核心内容】

硬件产品立项的核心内容 2.3 硬件产品立项的核心内容2.3.1 第一步&#xff1a;市场趋势判断2.3.2 第二步&#xff1a;竞争对手分析1.竞争对手识别2.根据竞争对手分析制定策略 2.3.3 第三步&#xff1a;客户分析2.3.4 第四步&#xff1a;产品定义2.3.5 第五步&#xff1a;开发执…

ReactPress数据库表结构设计全面分析

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 ReactPress是一个基于React框架开发的开源发布平台和内容管理系统&#xff08;CMS&#xff09;。它不仅支持用户在支持React和MySQL数据库的服务器上搭建自己的博客和网站&#…

ANDROIDWORLD: A Dynamic BenchmarkingEnvironment for Autonomous Agents论文学习

这个任务是基于androidenv的。这个环境之前学过&#xff0c;是一个用来进行强化学习的线上环境。而这篇文章的工作就是要给一些任务加上中间的奖励信号。这种训练环境的优点就是动态&#xff0c;与静态的数据集&#xff08;比如说我自己的工作&#xff09;不同&#xff0c;因此…

华为ENSP--ISIS路由协议

项目背景 为了确保资源共享、办公自动化和节省人力成本&#xff0c;公司E申请两条专线将深圳总部和广州、北京两家分公司网络连接起来。公司原来运行OSFP路由协议&#xff0c;现打算迁移到IS-IS路由协议&#xff0c;张同学正在该公司实习&#xff0c;为了提高实际工作的准确性和…