魔兽服务器学习-笔记(服务器部署、地图管理、DB、日志模块、任务模块、战斗模块)

文章目录

    • 一、环境准备
      • 1)依赖安装
      • 2)源码下载和编译
    • 二、生成数据信息
      • 1)地图数据信息(客户端信息)
      • 2)数据库信息
    • 三、启动服务器
    • 四、日志模块
    • 五、数据库模块
    • 六、场景模块
      • 1)地图管理
      • 2)AOI算法
        • 1、静态数据讲解
        • 2、动态数据:
        • 3、动态数据结构NGrid解析
        • 4、涉及用户登录的逻辑及grid状态转变
        • 5、涉及玩家退出的逻辑及grid状态转变
      • 3)地图数据驱动
        • 1)网络驱动
        • 2)地图定时更新做的事
      • 4)动态数据管理
        • (1)设计模式-访问者模式及代码表现形式
      • 5)碰撞检测实现(BIH+AABB)
    • 七、战斗模块
    • 八、MMO常用模块(任务、副本、商店等)

一、环境准备

1)依赖安装

sudo apt-get update
sudo apt-get install git clang cmake gcc g++ 
	libmysqlclient-dev libss-dev libbz2-dev libreadline-dev
	libncurses-dev libboost-all-dev mysql-server-5.7 p7zip

sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang 100

//
sudo yum install -y clang 


2)源码下载和编译

git clone -b 3.3.5 https//github.com//TrinityCore/TrinityCore.git

mkdir build cd build
cmake ../ -DCMAKE_INSTALL_PREFIX=/home/lighthouse/tinycore  
-DCONF_DIR=/home/lighthouse/tinycore/bin
make -j2  (nproc看核心数来编译)
make install

二、生成数据信息

1)地图数据信息(客户端信息)

1)cd ~
2)mkdir res  (根目录创建res文件夹)
3)把客户端目录的Data和Interface移动到res目录下

4)cd res
在res目录执行游戏目录bin/下面的mapextractor,
生成dbc和maps文件夹

5)mkdir vmaps
在res目录执行游戏目录bin/下面的vmap4extractor,
生成vmaps文件夹和Buildings目录

5)在res目录执行游戏目录bin/下面的vmap4assembler,
../lighthouse/bin/vmap4assembler Buildings vmaps

6) mkdir mmaps
在res目录执行游戏目录bin/下面的mmaps_generator
生成mmaps目录

2)数据库信息

  • 注意
    需要提前安装mysql
mysql -uroot -p password

1)在TrinityCore的sql/create目录的路径赋值,打开mysql

mysql> source ../Trinity/sql/create/create_mysql.sql

show database;

可以看到生成了auth \ character \ world

三、启动服务器

1)先进到项目的bin目录,复制authserver.conf

启动授权服务器
./authserver

2)同样步骤,复制worldserver.conf
并且改写DataDir,指定res目录

DataDir="../../res"

启动worldserver

启动授权服务器
./worldserver

四、日志模块

五、数据库模块

六、场景模块

1)地图管理

①哪些模块会用到地图模块?
1)运动模块:走、跳、飞行
2)副本
3)寻路
②地图模块要实现哪些功能?
1)AOI:管理地图地理信息、地图对象信息
2)功能:视野、数据同步、碰撞检测、寻路算法
③怎么驱动地图模块?
1)移动的网络数据驱动
2)定时更新(怪物的AI行为)

2)AOI算法

职责:
①静态数据:
《1》trinity由mapextractor生成.map文件(基础地图信息),数据包括

1) area data  (区域物体信息)
2) height data (高度信息)
3) liquid data (水)
4) hole data  (洞)

《2》由mmaps_generator生成可移动地图信息 .mmap,游戏中的地图移动数据(用来给navmesh寻路),也就是用recast和detour生成寻路信息

1) mmtile 索引对应具体地图的所有信息(x+y做名字前缀)
2) .mmap  索引

《3》vmapsextractor生成地图元素信息(可视场景信息:山脉、水体、建筑物等静态场景信息),用于未来做碰撞检测

*.m2和	*.wmo文件  静态物品包围盒信息
*.mdx  角色、物品和怪物包围盒信息

《4》vmap4assembler合并vmapsextractor和mmaps_generator生成的地图信息,vmtile信息

②动态数据

1、静态数据讲解
NGridType* i_grids[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS];   
//NGridType是动态数据
GridMap*   GridMaps[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS]; //原始数据,最新的已去除
//静态数据,64*64的格子,
//每个格子是8*8个cell,grid和cell都有自己的坐标系
//动物、角色、怪物都是在具体的cell当中 
std::bitset<TOTAL_NUMBER_OF_CELLS_PER_MAP*TOTAL_NUMBER_OF_CELLS_PER_MAP> marked_cells;
2、动态数据:

玩家在哪个grid的cell,哪个grid才会被加载,否则不会被加载到内存当中 (只有玩家进入这个区域才会动态加载这个区域的资源)

3、动态数据结构NGrid解析

①数据结构

//NGridType是模板类
extern template class NGrid<MAX_NUMBER_OF_CELLS, Player, AllWorldObjectTypes, AllGridObjectTypes>;
typedef NGrid<MAX_NUMBER_OF_CELLS, Player, AllWorldObjectTypes, AllGridObjectTypes> NGridType;

AllWorldObjectTypes:(动态的obj)
player玩家、宠物、尸体、动态物品(远处target)
typedef TYPELIST_4(Player, Creature/*pets*/, Corpse/*resurrectable*/, DynamicObject/*farsight target*/) 
AllWorldObjectTypes;

AllGridObjectTypes :(静态的obj)
游戏obj、除了宠物的obj、动态物体、尸体
typedef TYPELIST_7(GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation) AllGridObjectTypes;

②具体分析
NGrid有两个容器:i_container和i_objects

    private:
        uint32 i_gridId;
        GridInfo i_GridInfo;
        GridReference<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> > i_Reference;
        int32 i_x;
        int32 i_y;
        grid_state_t i_cellstate;
        GridType i_cells[N][N];
        bool i_GridObjectDataLoaded;

typedef Grid<Player, AllWorldObjectTypes, AllGridObjectTypes> GridType;
class Grid
    private:

        TypeMapContainer<GRID_OBJECT_TYPES> i_container;
        TypeMapContainer<WORLD_OBJECT_TYPES> i_objects;

玩家进入某个grid的cell,地图加载数据激活推动消息推送

4、涉及用户登录的逻辑及grid状态转变
MapRefManager m_mapRefManager;//MapRefManager双向循环链表


1)根据地图ID、xyz轴、面向在地图上准备new obj
2)把玩家链接到这个地图对应的双向循环链表上
3)根据玩家坐标创建一个cell,加载玩家到对应地图的内存上,若这个cell只有一个玩家则加载cell全部地图信息
4)把玩家的包围盒和怪物、NPC、玩家用BSP tree包围盒做碰撞检测(访问者模式)
DynamicMapTree _dynamicTree;
        void Balance() { _dynamicTree.balance(); }
5、涉及玩家退出的逻辑及grid状态转变
1)当定时器超时玩家没重连,那就把玩家从当前地图清除

⑤地图的状态

1)Idle
2)Active
3)InValid  //无效状态
4)REMOVAL //准备从内存中卸载
  • 地图状态转换说明:
    ①起伏创建是Invalid,
    ②加载具体cell到grid里面是Idle状态,
    ③当把玩家放进来的时候是Active状态,
    ④当玩家退出或掉线时候,这个cell是无人的时候,变为Idle状态
    ⑤当超时重连结束,玩家被从地图删除实例,则是Removal状态,下次服务器tick触发的时候把地图从内存卸载
    (魔兽主线程等待子线程update地图事件,全部结束后才往下把所有map走delayUpdate,更新地图状态机,走从内存卸载删除逻辑)
typedef enum
{
    GRID_STATE_INVALID = 0,
    GRID_STATE_ACTIVE = 1,
    GRID_STATE_IDLE = 2,
    GRID_STATE_REMOVAL= 3,
    MAX_GRID_STATE = 4
} grid_state_t;

3)地图数据驱动

玩家进入某个grid的cell,地图加载数据激活推动消息推送

1)网络驱动

在这里插入图片描述

  • 网络结构说明
    ①创线程池A,线程主要做:
1)信号处理
2)远程连接请求
3)数据库心跳
4)生成core检查
5)异步日志
...

②主线程有主循环,按照一定频率update游戏世界逻辑,执行的逻辑比如

1)更新玩家session信息,根据玩家连接状态更改session状态
(如果不是线程安全,那就在主线程处理,后面的业务逻辑是多线程处理的)
2)主线程处理的逻辑包括:从数据库获取基础的玩家初始数据比如背包等
3)其他线程中的处理:在地图中攻击、寻路、走路

③线程池B,主要处理地图数据
读取配置中的线程数量,创建线程池,并给每条线程发更新地图的放到阻塞的任务队列,每条map也能处理来自客户端发送的数据包

void MapManager::Update(uint32 diff)
{
    i_timer.Update(diff);
    if (!i_timer.Passed())
        return;

    MapMapType::iterator iter = i_maps.begin();
    while (iter != i_maps.end())
    {
        if (iter->second->CanUnload(diff))
        {
            if (DestroyMap(iter->second))
                iter = i_maps.erase(iter);
            else
                ++iter;

            continue;
        }

        if (m_updater.activated())
            m_updater.schedule_update(*iter->second, uint32(i_timer.GetCurrent()));
        else
            iter->second->Update(uint32(i_timer.GetCurrent()));

        ++iter;
    }
    if (m_updater.activated())
        m_updater.wait();

    for (iter = i_maps.begin(); iter != i_maps.end(); ++iter)
        iter->second->DelayedUpdate(uint32(i_timer.GetCurrent()));

    i_timer.SetCurrent(0);
}

④线程池C用来负载均衡处理客户端发包(Network)

2)地图定时更新做的事

①更新这个地图中的玩家,用访问者模式给玩家广播周围的obj(让自己在同cell里面被其他人看到),处理视野相关内容
②包围盒_dynamicTree二叉树检测物体碰撞(惰性更新,每次更新的时候进行平衡)

void Map::Update(uint32 t_diff)
{
    _dynamicTree.update(t_diff); //包围盒检测
    /// update worldsessions for existing players
    for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter)
    {
        Player* player = m_mapRefIter->GetSource();
        if (player && player->IsInWorld())
        {
            //player->Update(t_diff);
            WorldSession* session = player->GetSession();
            MapSessionFilter updater(session);
            session->Update(t_diff, updater);
        }
    }
    ...
}

③处理人物的重生计时

    /// process any due respawns
    if (_respawnCheckTimer <= t_diff)
    {
        ProcessRespawns();
        UpdateSpawnGroupConditions();
        _respawnCheckTimer = sWorld->getIntConfig(CONFIG_RESPAWN_MINCHECKINTERVALMS);
    }
    else
        _respawnCheckTimer -= t_diff;

④玩家战斗中技能释放、副本流程、人物、成就的更新

4)动态数据管理

(1)设计模式-访问者模式及代码表现形式
  • 概念
    ①提供一个访问者类,可以改变元素类的执行算法
    ②传入不同的访问者,可以使用不同的执行算法
  • 解决的问题
    稳定的数据结构和容易改变的操作耦合问题
  • 解决方法
    在元素类型中提供对应不同访问者的接口
  • 目的
    将数据结构和数据操作进行分离
  • 代码
    T是算法,CONTAINER是数据结构
1)TypeContainer.h  
有两种结构:双向链表和散列表,解决不同链表、不同散列表的差异
2)TypeContainerFunctions.h 
解决双向链表和散列表之间的差异
3)TypeCOntainerVistor.h 
解决对访问者的抽象,不同访问者实现不同的算法

template<class T, class CONTAINER>
inline void Map::Visit(Cell const& cell, TypeContainerVisitor<T, CONTAINER>& visitor)
{
    const uint32 x = cell.GridX();
    const uint32 y = cell.GridY();
    const uint32 cell_x = cell.CellX();
    const uint32 cell_y = cell.CellY();

    if (!cell.NoCreate() || IsGridLoaded(GridCoord(x, y)))
    {
        EnsureGridLoaded(cell);
        getNGrid(x, y)->VisitGrid(cell_x, cell_y, visitor);
    }
}

通过下面这个函数将不同obj加入到不同的双向链表里面

1)不同双向链表:
        TypeMapContainer<GRID_OBJECT_TYPES> i_container;
        TypeMapContainer<WORLD_OBJECT_TYPES> i_objects;



2)访问者算法加入不同链表的算法
template<class OBJECT>
class GridReference : public Reference<GridRefManager<OBJECT>, OBJECT>
{
    protected:
        void targetObjectBuildLink() override
        {
            // called from link()
            this->getTarget()->insertFirst(this);
            this->getTarget()->incSize();
        }
        void targetObjectDestroyLink() override
        {
            // called from unlink()
            if (this->isValid()) this->getTarget()->decSize();
        }
        void sourceObjectDestroyLink() override
        {
            // called from invalidate()
            this->getTarget()->decSize();
        }
    public:
        GridReference() : Reference<GridRefManager<OBJECT>, OBJECT>() { }
        ~GridReference() { this->unlink(); }
        GridReference* next() { return (GridReference*)Reference<GridRefManager<OBJECT>, OBJECT>::next(); }
};


3)不同类型的访问者做不同处理,visit就是不同的算法
class TC_GAME_API ObjectGridLoader : public ObjectGridLoaderBase
{
    friend class ObjectWorldLoader;

    public:
        ObjectGridLoader(NGridType& grid, Map* map, Cell const& cell)
            : ObjectGridLoaderBase(grid, map, cell)
            { }

        void Visit(GameObjectMapType &m);
        void Visit(CreatureMapType &m);
        void Visit(AreaTriggerMapType &m);
        void Visit(CorpseMapType &) const { }        //尸体obj
        void Visit(DynamicObjectMapType&) const { }  //动态物品,掉落装备等
        void Visit(SceneObjectMapType&) const { }    //场景对象
        void Visit(ConversationMapType&) const { }   //对话obj

        void LoadN();
};

5)碰撞检测实现(BIH+AABB)

  • 碰撞检测目标
    对象模型的包围盒
  • 数据来源
    vmap4extractor提取的场景信息(建筑、山脉、水体等)、角色、怪物、装备等3d模型信息
  • 逻辑流程
    1)是否在视线范围内
    2)碰撞检测,算命中位置(如不同楼层等高度信息)
    3)获取地图信息
  • 怎么实现?(一句话:射线和包围盒的碰撞)
    ①插入的时候会平衡一次
    ②然后就是定时200ms平衡一次
  • 实现代码
class TC_COMMON_API DynamicMapTree
{
    DynTreeImpl *impl;

public:

    DynamicMapTree();
    ~DynamicMapTree();

    bool isInLineOfSight(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, PhaseShift const& phaseShift) const;
    bool getIntersectionTime(G3D::Ray const& ray, G3D::Vector3 const& endPos, PhaseShift const& phaseShift, float& maxDist) const;
    bool getObjectHitPos(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, G3D::Vector3& resultHitPos, float modifyDist, PhaseShift const& phaseShift) const;

	//看层高
    float getHeight(float x, float y, float z, float maxSearchDist, PhaseShift const& phaseShift) const;
    bool getAreaInfo(float x, float y, float& z, PhaseShift const& phaseShift, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
    void getAreaAndLiquidData(float x, float y, float z, PhaseShift const& phaseShift, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const;

	//添加、删除、看是否存在
    void insert(GameObjectModel const&);
    void remove(GameObjectModel const&);
    bool contains(GameObjectModel const&) const;

	//更新就是平衡
    void balance();
    void update(uint32 diff); //定时平衡,200ms平衡一次
};

  • 核心算法和数据结构
    数据结构是BIH树,核心算法是AABB(轴对称边界盒算法,射线和包围盒相交的算法),BIH作为二叉树是为了快速进行流程,避免费时计算
    在这里插入图片描述
  • 如何实现BIH树?
    1、构建BIH树
    2、光线追踪算法AABB(AABB并不是稳定的结构,可能随时会有新obj进来)
  • 碰撞计算逻辑
    1、计算包围盒(vmap数据中进行加载)
    2、在一个grid中所有对象根据中轴线构建二叉树,左边为左子树,右边为右子树,判断节点方块与光线是否相交,若相交则不断递归下去,直到满足递归停止条件(递归深度64)
    3、注意:每个节点都是子树对象包围盒的并集
    4、射线就是行走路径
    在这里插入图片描述
  • 优化
    ①初始时构建一次
    ②定时构建:

1、200ms构建一次
2、如果BIH树发生改变才会重新构建

七、战斗模块

八、MMO常用模块(任务、副本、商店等)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在微信内置浏览器内抓包

文章目录 使用环境&工具使用步骤1、手机USB连接上电脑&#xff0c;打开USB调试2、解压adb工具的压缩包&#xff0c;使用该工具连接上手机3、开启微信抓包4、电脑上打开chrome内核的浏览器或edge浏览器 使用环境&工具 windows电脑 安卓手机 adb工具 USB数据线 使用步骤…

【已解决】git push send-pack: unexpected disconnect while reading sideband packet

解决办法&#xff1a;修改缓存大小 打开项目所在路径下的git目录 找到config文件&#xff0c;用记事本打开编辑。 添加如下内容并保存即可 [http] postBuffer 1048576000

每日一练:Python中实现将阳历转换为农历

农历是中国传统的农业历法&#xff0c;与阳历&#xff08;公历&#xff09;有所不同。在Python中&#xff0c;我们可以使用第三方库 lunardate 来实现阳历到农历的转换。以下是一个简单的示例&#xff0c;演示如何在Python中执行这个转换过程。 1.安装 lunardate 库 首先&…

VR全景:打造虚拟政务服务,打通服务群众“最后一公里”

大家对政务大厅的工作效率可能已经司空见惯&#xff0c;办事窗口少&#xff0c;而需要办理的群众和业务却很多&#xff0c;很多去政务大厅办理业务的&#xff0c;排队几个小时也是常有的。并且在传统政务服务中&#xff0c;办事流程一般都较为复杂、耗时长&#xff0c;往往需要…

基于SSM的宠物领养系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

高频CSS面试题

给大家推荐一个实用面试题库 1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;web前端面试题库 BFC 块级格式上下文(block format context)是页面一块独立的渲染区域&#xff0c;具有一套独立的渲染规则 内部的…

吊打Fast Request还免费? 这款插件真心好用!

今天给大家推荐一款IDEA插件&#xff1a;Apipost Helper&#xff0c;比Fast Request更好用并且完全免费&#xff01;三大亮点功能&#xff1a;写完代码IDEA内一键生成API文档&#xff1b;写完代码IDEA内一键调试&#xff0c;&#xff1b;生成API目录树&#xff0c;双击即可快速…

[RK3568][Android12.0]--- 系统自带预置第三方APK方法

Platform: RK3568 OS: Android 12.0 Kernel: 4.19 Rockchip默认提供了机制来预置第三方APK, 方法很简单&#xff1a; 1. 在device/rockchip/rk3568创建preinstall目录(如果要可卸载&#xff0c;那就创建preinstall_del目录) 2. 将你要预安装的APK放进此目录即可 preinstall 不…

wind版本elasticdump执行报错 unexpected token ‘ in json at

输入的格式不对&#xff1a; 之前&#xff0c;json格式不对&#xff1a; elasticdump --inputhttp://***:9200/d_*_news, --output/home/zyyt/es_data_bak/0714.json --searchBody{"query":{"bool":{"must":[{"term":{"languag…

【算法练习Day48】回文子串最长回文子序列

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 回文子串最长回文子序列总结…

SparkSQL自定义UDF函数

需求&#xff1a;员工id正常为8位&#xff0c;对于不满8位的员工id左侧用0补齐 import org.apache.spark.sql.{DataFrame, SparkSession}object DataSetCreate {def main(args: Array[String]): Unit {val spark SparkSession.builder().appName("test").master(&…

excel用RAND函数、或者RAND.NV函数生成随机数、这两个函数的区别

用RAND函数生成大于0小于1的随机数 插入-》函数&#xff1a; 选择RAND函数&#xff1a; 点击“继续”&#xff1a; 点击“确定”&#xff0c;就生成随机数了&#xff1a; 用RAND.NV函数生成一个大于0小于1的随机数 步骤跟RAND函数相同&#xff0c;只不过选择的是RAN…

Eclipse使用配置tomcat服务:Server配置

目标&#xff1a; 在Eclipse中&#xff0c;默认会把Web项目放到Eclipse的工作空间下 的.metadata\.plugins\org.eclipse.wst.server.core\tmp0(或者是tmp1)\wtpwebapps\下 &#xff0c;如果现在Eclipse中有名为imsmanagere的项目&#xff0c;将它按以前的方式部署到服务器上&am…

2023.11.14使用bootstrap制作一个简洁的前端注册登录页

2023.11.14使用bootstrap制作一个简洁的前端注册登录页 比较简洁的登录页&#xff0c;主要是为自己开发的一些平台页面做测试用&#xff0c;前端具备功能如下&#xff1a; &#xff08;1&#xff09;输入用户名、密码&#xff0c;需补充后端验证代码。 &#xff08;2&#xff…

下载文件时的文件名中文乱码问题,文件名丢失

涉及到的java代码如下&#xff0c;下载的时候文件名为中文 package com.example.springboot.service.impl;import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringU…

【nlp】2.4 GRU模型

GRU模型 1 GRU介绍2 GRU的内部结构图2.1 GRU结构分析2.2 Bi-GRU介绍2.3 使用Pytorch构建GRU模型2.4 GRU优缺点3 RNN及其变体1 GRU介绍 GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆…

上海国际集团党委副书记、总裁刘信义一行莅临ZStack调研指导

11月10日&#xff0c;上海国际集团有限公司党委副书记、总裁刘信义率上海国际集团、上海国资经营及国鑫创投领导莅临上海云轴信息科技有限公司&#xff08;简称“云轴科技ZStack”&#xff09;调研指导&#xff0c;云轴科技ZStack创始人、董事长张鑫&#xff0c;携公司管理团队…

最新宝塔反代openai官方API开发接口详细搭建教程,解决502 Bad Gateway问题

一、前言 宝塔反代openai官方API接口详细教程&#xff0c;实现国内使用ChatGPT502 Bad Gateway问题解决&#xff0c; 此方法最简单快捷&#xff0c;没有复杂步骤&#xff0c;不容易出错&#xff0c;即最简单&#xff0c;零代码、零部署的方法。 二、实现前提 一台海外服务器…

鸿蒙系统扫盲(一):鸿蒙OS和开源鸿蒙什么关系?

我们经常提到鸿蒙&#xff0c;但是大家都分不清鸿蒙OS&#xff08;Harmony OS&#xff09;和 开源鸿蒙&#xff08;Open Harmony&#xff09;的区别&#xff1f; 1.开源鸿蒙&#xff08;Open Harmony&#xff09; 鸿蒙系统愿来的设计初衷&#xff0c;就是让所有设备都可以运行…

【C++】join ()和detach ()函数详解和示例

简单的来说&#xff0c;join ()方法建立的线程具有阻碍作用&#xff0c;该线程不结束&#xff0c;另一些函数就无法运行。detach ()方法建立的线程&#xff0c;可以和另一些函数同时进行。下面以示例进行详细说明&#xff0c;以帮助大家理解和使用。 目录 join ()detach () jo…