C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发004:游戏核心消息处理 - 玩家类的实现
文章目录
- 0 代码仓库
- 1 需求
- 2 AOI设计
- 2.1 AOI算法简介
- 2.2 AOI数据结构及实现
- 2.2.1 玩家
- 2.2.2 网格对象
- 2.2.3 游戏世界矩形
- 2.2.4 获取周围玩家的实现
- 2.2.5 代码测试
- 2.3 GameRole结合AOI创建玩家
- 2.3.1 创建游戏世界全局对象-GameRole继承AOIWorld的Player
- 2.3.2 把玩家到游戏世界的加入与删除
- 2.3.3 玩家上线时的处理:新客户端连接后,向自己发送ID和名称
- 2.3.4 新客户端连接后,向其发送**周围**玩家的位置
- 2.3.5 新客户端连接后,向**周围**玩家发送其位置
- 2.3.6 游戏测试:
- 2.3.7 世界聊天
- 2.3.7.1 创建广播
- 2.3.7.2 发送给所有人
- 2.4 玩家移动处理
- 2.4.1 视野出现和消失
- 2.4.2 跨网格处理
- 2.4.3 广播新位置给周围玩家
- 2.5 随机出生
- 2.7 退出程序
- 2.7.1 定时器设计
- 2.7.2 最后一个玩家,启动定时器
- 2.7.3 初始化的完善
- 2.7.4 主函数的完善
- 2.8 随机姓名的设计与实现
- 2.8.1 姓和常用名的定义
- 2.8.2 取名字
- 2.8.3 还名字
- 2.8.4 读取文件组建姓名的线性表
- 3 架构回顾
游戏相关的核心消息处理逻辑都是要在该类中实现的。
0 代码仓库
https://github.com/Chufeng-Jiang/TCP_Concurrent_Server_Framework_Zinx-Game_Server_Development_Project
1 需求
- 新客户端连接后,向其发送ID和名称
- 新客户端连接后,向其发送周围玩家的位置
- 新客户端连接后,向周围玩家发送其位置
- 收到客户端的移动信息后,向周围玩家发送其新位置
- 收到客户端的移动信息后,向其发送周围新玩家位置
- 收到客户端的聊天信息后,向所有玩家发送聊天内容
- 客户端断开时,向周围玩家发送其断开的消息
关键字:周围
。
以上所列出的需求,基本都是这样的套路:在XXX的时候,发送XXX给XXX。
- 发送时机
- 消息内容
- 发送对象:怎样表示周围玩家?
2 AOI设计
2.1 AOI算法简介
定义: 获取感兴趣的区域(Area Of Interest)的算法。
解决的问题: 形成周围的概念。在多人游戏中,各个游戏客户端之间需要通过服务器向彼此更新自身状态。但对于当玩家来说,我们不需要获取“太远”的玩家的信息,所以,在服务器端,我们通过AOI算法可以获取到某个客户端“周围”的玩家,进而只在该小范围内同步信息。
网格法AOI:
- 参考游戏世界的坐标,创建一个边界相同的矩形。
- 选取适当的颗粒度,将矩形分割成几×几的网格。
- 每个客户端都要按照实际坐标添加到某个格子里。
- 客户端所在格子的
周围八个格子
内的玩家就是周围玩家。
举例: 世界坐标是X[20,200],Y[50,230],划分成6×6的网格为:
- 已知玩家坐标(x,y),该玩家在几号网格?
- 网格编号=(x-x轴起始坐标)/x轴网格宽度 + (y-y轴起始坐标)/y轴宽度*x轴网格数量
- x轴网格宽度=(x轴结束坐标-x轴起始坐标)/x轴网格数量;
- y轴的计算方式相同
- 已知玩家在n号网格,周围的格子(包括自己)有哪些?
2.2 AOI数据结构及实现
目的:获取周围玩家
模型:将游戏世界的坐标分割成网格,玩家属于某个网格
周围:玩家所属网格周围8个相邻网格内的玩家
游戏世界矩形:包含固定数量网格对象的容器
网格对象:包含若干玩家的容器
玩家:拥有横纵坐标的对象
2.2.1 玩家
class Player {
public:virtual int GetX() = 0;virtual int GetY() = 0;
};
2.2.2 网格对象
- 添加玩家的时候,计算出玩家的坐标,然后将坐标就添加到网格对象中。
- 网格对象是以网格为单位的矩形,里面装的是该网格范围内的所有玩家(坐标)。
class Grid {
public:std::list<Player *> m_players;
};
- 添加玩家到网格对象
bool AOIWorld::AddPlayer(Player * _player)
{/*计算所属网格号*///网格编号=(x-x轴起始坐标)/x轴网格宽度 + (y-y轴起始坐标)/y轴宽度*x轴网格数量int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY()-y_begin) / y_width * x_count;/*添加到该网格中*/m_grids[grid_id].m_players.push_back(_player);return true;
}
- 删除玩家
void AOIWorld::DelPlayer(Player * _player)
{int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY() - y_begin) / y_width * x_count;m_grids[grid_id].m_players.remove(_player);
}
2.2.3 游戏世界矩形
class AOIWorld
{int x_begin = 0;int x_end = 0;int y_begin = 0;int y_end = 0;int x_count = 0;int y_count = 0;int x_width = 0;int y_width = 0;
public:std::vector<Grid> m_grids;/*通过构造函数指定矩形的大小和分割粒度*/AOIWorld(int _x_begin, int _x_end, int _y_begin, int _y_end, int _x_count, int _y_count);virtual ~AOIWorld();/*获取周围玩家*/std::list<Player *> GetSrdPlayers(Player *_player);/*添加玩家到AOI网格*/bool AddPlayer(Player *_player);/*摘除玩家*/void DelPlayer(Player *_player);
};
- 初始化世界并创建格子对象
AOIWorld::AOIWorld(int _x_begin, int _x_end, int _y_begin, int _y_end, int _x_count, int _y_count):x_begin(_x_begin),x_end(_x_end),y_begin(_y_begin),y_end(_y_end),x_count(_x_count),y_count(_y_count)
{//x轴网格宽度=(x轴结束坐标-x轴起始坐标)/x轴网格数量;y轴的计算方式相同x_width = (x_end - x_begin) / x_count;y_width = (y_end - y_begin) / y_count;/*创建格子们*/for (int i = 0; i < x_count * y_count; i++){Grid tmp;m_grids.push_back(tmp);}
}
2.2.4 获取周围玩家的实现
std::list<Player*> AOIWorld::GetSrdPlayers(Player * _player)
{list<Player *> ret;/*计算所属编号*/int grid_id = (_player->GetX() - x_begin) / x_width + (_player->GetY() - y_begin) / y_width * x_count;/*判断具体情况,取出邻居网格的玩家们*///计算当前网格横着数和纵着数的个数, 当前网格在世界的坐标int x_index = grid_id % x_count; //横着的坐标,colint y_index = grid_id / x_count; //纵向坐标,rowif (x_index > 0 && y_index > 0) //有左上角的格子{list<Player *> &cur_list = m_grids[grid_id - 1 - x_count].m_players;ret.insert(ret.begin(), cur_list.begin(),cur_list.end());}if (y_index > 0) //正上方的格子{list<Player *> &cur_list = m_grids[grid_id - x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index < x_count - 1 && y_index > 0) //右上角的格子{list<Player *> &cur_list = m_grids[grid_id - x_count + 1].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index > 0) //左方的格子{list<Player *> &cur_list = m_grids[grid_id - 1].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}//自己所在位置list<Player *> &cur_list = m_grids[grid_id].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());if (x_index < x_count - 1) //右方的格子{list<Player *> &cur_list = m_grids[grid_id +1 ].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index > 0 && y_index < y_count - 1) //左下方的格子{list<Player *> &cur_list = m_grids[grid_id - 1 + x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (y_index < y_count - 1) //正下方的格子{list<Player *> &cur_list = m_grids[grid_id + x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}if (x_index < x_count - 1 && y_index < y_count - 1) //右下方的格子{list<Player *> &cur_list = m_grids[grid_id + 1+x_count].m_players;ret.insert(ret.begin(), cur_list.begin(), cur_list.end());}return ret;
}
2.2.5 代码测试
#include "GameChannel.h"
#include "GameMsg.h"
#include "msg.pb.h"
#include "AOIWorld.h"class myPlayer :public Player {
public:myPlayer(int _x, int _y, std::string _name) :x(_x), y(_y), name(_name) {}int x;int y;std::string name;// 通过 Player 继承virtual int GetX() override{return x;}virtual int GetY() override{return y;}
};int main()
{AOIWorld w(20, 200, 50, 230, 6, 6);myPlayer p1(60, 107, "1");myPlayer p2(91, 118, "2");myPlayer p3(147, 133, "3");w.AddPlayer(&p1);w.AddPlayer(&p2);w.AddPlayer(&p3);auto srd_list = w.GetSrdPlayers(&p1);for (auto single : srd_list){std::cout << dynamic_cast<myPlayer*>(single)->name << std::endl;}ZinxKernel::ZinxKernelInit();/*添加监听通道*/ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));ZinxKernel::Zinx_Run();ZinxKernel::ZinxKernelFini();
}
2.3 GameRole结合AOI创建玩家
2.3.1 创建游戏世界全局对象-GameRole继承AOIWorld的Player
proto文件中对应
message Position{float X=1;float Y=2; float Z=3; float V=4;int32 BloodValue=5;
}
#pragma once
#include <zinx.h>
#include "AOIWorld.h"
#include "GameMsg.h"class GameProtocol;
class GameRole :public Irole,public Player
{float x = 0;float y = 0;//高float z = 0;float v = 0;int iPid = 0;std::string szName;GameMsg *CreateIDNameLogin();GameMsg *CreataSrdPlayers();GameMsg *CreateSelfPostion();GameMsg *CreateIDNameLogoff();
public:// 通过 Player 继承virtual int GetX() override;virtual int GetY() override;
};
- 注意在人物角色中,y是人物的高度,不是二维平面的地点坐标,y由前端那边设计。
/*创建游戏世界全局对象*/
static AOIWorld world(0, 400, 0, 400, 20, 20);int GameRole::GetX()
{return (int)x;
}int GameRole::GetY()
{return (int)z;
}
2.3.2 把玩家到游戏世界的加入与删除
- 连接到来(玩家初始化)时
属性pid赋值为socket值
属性name写成tom
初始坐标100,100
向自己发内容是ID和姓名的1号消息
向自己发内容是若干周围玩家信息的202号消息
向周围玩家发送内容是自己位置的200号消息
bool GameRole::Init()
{/*添加自己到游戏世界*/bool bRet = false;/*设置玩家ID为当前连接的fd*/iPid = m_pProto->m_channel->GetFd(); //获取文件连接描述符,这个是唯一的bRet = world.AddPlayer(this);if (true == bRet){/*向自己发送ID和名称*/auto pmsg = CreateIDNameLogin();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向自己发送周围玩家的位置*/pmsg = CreataSrdPlayers();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向周围玩家发送自己的位置*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){pmsg = CreateSelfPostion();auto pRole = dynamic_cast<GameRole *>(single);// 注意第二个参数ZinxKernel::Zinx_SendOut(*pmsg, *(pRole->m_pProto));}}return bRet;
}void GameRole::Fini()
{/*向周围玩家发送下线消息*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){auto pMsg = CreateIDNameLogoff();auto pRole = dynamic_cast<GameRole *>(single);ZinxKernel::Zinx_SendOut(*pMsg, *(pRole->m_pProto));}world.DelPlayer(this);
}
2.3.3 玩家上线时的处理:新客户端连接后,向自己发送ID和名称
GameMsg * GameRole::CreateIDNameLogin()
{pb::SyncPid *pmsg = new pb::SyncPid();pmsg->set_pid(iPid);pmsg->set_username(szName);GameMsg *pRet = new GameMsg(GameMsg::MSG_TYPE_LOGIN_ID_NAME, pmsg);return pRet;
}
2.3.4 新客户端连接后,向其发送周围玩家的位置
- 设置protobuf类型消息的repeated类型
- add_XXXX函数
- 调用后,会向当前消息添加一个数组成员,返回数组成员的指针
GameMsg * GameRole::CreataSrdPlayers()
{pb::SyncPlayers *pMsg = new pb::SyncPlayers();auto srd_list = world.GetSrdPlayers(this);//周围玩家有多个for (auto single : srd_list){auto pPlayer = pMsg->add_ps();auto pRole = dynamic_cast<GameRole *>(single);//设置到遍历到的玩家的信息pPlayer->set_pid(pRole->iPid); pPlayer->set_username(pRole->szName);//把子消息挂到父消息里面,并返回子消息的指针auto pPostion = pPlayer->mutable_p();pPostion->set_x(pRole->x);pPostion->set_y(pRole->y);pPostion->set_z(pRole->z);pPostion->set_v(pRole->v);}GameMsg *pret = new GameMsg(GameMsg::MSG_TYPE_SRD_POSTION, pMsg);return pret;
}
2.3.5 新客户端连接后,向周围玩家发送其位置
message BroadCast{int32 Pid=1;int32 Tp=2;/*根据Tp不同,Broadcast消息会包含:聊天内容(Content)或初始位置(P)或新位置P*/oneof Data{string Content=3;Position P=4;/*ActionData暂时预留*/int32 ActionData=5;}string Username=6;
}
GameMsg * GameRole::CreateSelfPostion()
{pb::BroadCast *pMsg = new pb::BroadCast();pMsg->set_pid(iPid);pMsg->set_username(szName);pMsg->set_tp(2); //客户端决定的要设置成2auto pPosition = pMsg->mutable_p();pPosition->set_x(x);pPosition->set_y(y);pPosition->set_z(z);pPosition->set_v(v);GameMsg *pret = new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pMsg);return pret;
}
2.3.6 游戏测试:
在程序安装文件夹下进入控制台运行客户端
client.exe 192.168.111.135 8899
鼠标右键控制V
2.3.7 世界聊天
2.3.7.1 创建广播
class GameRole :public Irole,public Player
{void ProcTalkMsg(std::string _content);void ProcMoveMsg(float _x, float _y, float _z, float _v);void ViewAppear(GameRole *_pRole);void ViewLost(GameRole *_pRole);GameMsg *CreateTalkBroadCast(std::string _content);
};
2.3.7.2 发送给所有人
void GameRole::ProcTalkMsg(std::string _content)
{//发给所有人auto role_list = ZinxKernel::Zinx_GetAllRole();for (auto pRole : role_list){auto pGameRole = dynamic_cast<GameRole *>(pRole);auto pmsg = CreateTalkBroadCast(_content);ZinxKernel::Zinx_SendOut(*pmsg, *(pGameRole->m_pProto));}
}
- MSG_TYPE_BROADCAST 200号消息
GameMsg * GameRole::CreateTalkBroadCast(std::string _content)
{pb::BroadCast *pmsg = new pb::BroadCast();pmsg->set_pid(iPid);pmsg->set_username(szName);pmsg->set_tp(1);pmsg->set_content(_content);GameMsg *pRet = new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pmsg);return pRet;
}
2.4 玩家移动处理
- 广播新位置给周围玩家
- 若跨网格,视野切换(获取移动前周围玩家S1,获取移动后的周围 玩家S2)
- 新邻居:互相能看见({x|x 属于S2 && x 不属于S1})—>发送200号消息
- 旧邻居:互相看不见({x|x属于S1 && x不属于S2})—>201号消息
UserData * GameRole::ProcMsg(UserData & _poUserData)
{GET_REF2DATA(MultiMsg, input, _poUserData);for (auto single : input.m_Msgs){/*测试:打印消息内容*/cout << "type is" << single->enMsgType << endl;cout << single->pMsg->Utf8DebugString() << endl;auto NewPos = dynamic_cast<pb::Position *>(single->pMsg);switch (single->enMsgType){case GameMsg::MSG_TYPE_CHAT_CONTENT:ProcTalkMsg(dynamic_cast<pb::Talk *>(single->pMsg)->content());break;case GameMsg::MSG_TYPE_NEW_POSTION:ProcMoveMsg(NewPos->x(), NewPos->y(),NewPos->z(),NewPos->v());break;default:break;}}return nullptr;
}
2.4.1 视野出现和消失
void GameRole::ViewAppear(GameRole * _pRole)
{/*向自己发参数的200消息*/auto pmsg = _pRole->CreateSelfPostion();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向参数玩家发自己的200消息*/pmsg = CreateSelfPostion();ZinxKernel::Zinx_SendOut(*pmsg, *(_pRole->m_pProto));
}void GameRole::ViewLost(GameRole * _pRole)
{/*向自己发送参数玩家的201消息*/auto pmsg = _pRole->CreateIDNameLogoff();ZinxKernel::Zinx_SendOut(*pmsg, *m_pProto);/*向参数玩家发自己的201消息*/pmsg = CreateIDNameLogoff();ZinxKernel::Zinx_SendOut(*pmsg, *(_pRole->m_pProto));
}
2.4.2 跨网格处理
- 获取原来的邻居s1。
- 摘出旧格子,更新坐标,添加新格子,获取新邻居s2。
- 遍历s2,若元素不属于s1, 视野出现。
- 遍历s1,若元素不属于s2,视野消失。
void GameRole::ProcMoveMsg(float _x, float _y, float _z, float _v)
{/*1.跨网格处理*//*获取原来的邻居s1*/auto s1 = world.GetSrdPlayers(this);/*摘出旧格子*/world.DelPlayer(this);/*更新坐标,添加新格子,获取新邻居s2*/x = _x;y = _y;z = _z;v = _v;world.AddPlayer(this);auto s2 = world.GetSrdPlayers(this);/*遍历s2,若元素不属于s1, 视野出现*/for (auto single_player : s2){if (s1.end() == find(s1.begin(), s1.end(), single_player)){//视野出现ViewAppear(dynamic_cast<GameRole *>(single_player));}}/*遍历s1,若元素不属于s2,视野消失*/for (auto single_player : s1){if (s2.end() == find(s2.begin(), s2.end(), single_player)){//视野消失ViewLost(dynamic_cast<GameRole *>(single_player));}}
2.4.3 广播新位置给周围玩家
/*2.广播新位置给周围玩家*/ //遍历周围玩家发送/*向周围玩家发送自己的位置*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){//组成待发送的报文pb::BroadCast *pMsg = new pb::BroadCast();auto pPos = pMsg->mutable_p();pPos->set_x(_x);pPos->set_y(_y);pPos->set_z(_z);pPos->set_v(_v);pMsg->set_pid(iPid);pMsg->set_tp(4);pMsg->set_username(szName);auto pRole = dynamic_cast<GameRole *>(single);//封装成消息发出去ZinxKernel::Zinx_SendOut(*(new GameMsg(GameMsg::MSG_TYPE_BROADCAST, pMsg)), *(pRole->m_pProto));}
}
2.5 随机出生
设计: GameRole对象创建时,随机生成合理范围内的坐标。
生成随机数的方法:std::default_random_engine
详细资料:http://www.cplusplus.com/reference/random/default_random_engine/
- 构造函数参数用于指定种子(一般使用当前时间)
- ()操作符返回随机数(无符号整形)
static default_random_engine random_engine(time(NULL));GameRole::GameRole()
{szName = random_name.GetName();x = 100 + random_engine() % 50;z = 100 + random_engine() % 50;
}
2.7 退出程序
- 玩家全部退出后20s后服务器退出
创建定时任务:20秒周期,超时处理–》退出框架
添加时机:玩家fini的时候若总玩家 == 1
摘除时机:玩家init的时候若总玩家 == 0
2.7.1 定时器设计
class ExitTimer :public TimerOutProc {// 通过 TimerOutProc 继承virtual void Proc() override{ZinxKernel::Zinx_Exit();}virtual int GetTimeSec() override{return 20;}
};
static ExitTimer g_exit_timer;
2.7.2 最后一个玩家,启动定时器
void GameRole::Fini()
{/*向周围玩家发送下线消息*/auto srd_list = world.GetSrdPlayers(this);for (auto single : srd_list){auto pMsg = CreateIDNameLogoff();auto pRole = dynamic_cast<GameRole *>(single);ZinxKernel::Zinx_SendOut(*pMsg, *(pRole->m_pProto));}world.DelPlayer(this);/*判断是否是最后一个玩家--->起定时器*/if (ZinxKernel::Zinx_GetAllRole().size() <= 1){//起退出定时器TimerOutMng::GetInstance().AddTask(&g_exit_timer);}
}
2.7.3 初始化的完善
bool GameRole::Init()
{if (ZinxKernel::Zinx_GetAllRole().size() <= 0){TimerOutMng::GetInstance().DelTask(&g_exit_timer);}
......
}
2.7.4 主函数的完善
int main()
{ZinxKernel::ZinxKernelInit();/*添加监听通道*/ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));// 添加定时器ZinxKernel::Zinx_Add_Channel(*(new ZinxTimerChannel()));ZinxKernel::Zinx_Run();ZinxKernel::ZinxKernelFini();
}
2.8 随机姓名的设计与实现
设计: 在文件中存储一定量的常用姓和名,GameRole创建时随机组合姓名
线性表存姓和名组成的线性表
取名字:随机取姓,随机取名
还名字:尾部追加姓或名
读姓文件的同时读名文件,边追加节点
- 设计数据结构存储随机姓名池,进程启动时构造
- 生成随机名称:取第随机个姓,取第随机个名
- 进程启动时读取文件构建姓名池
- 玩家类构造时从姓名池获取姓名
- 玩家类析构时释放姓名回姓名池
2.8.1 姓和常用名的定义
//姓和 名组成的线性表
class FirstName {
public:std::string m_first;//使用vectorstd::vector<std::string> m_last_list;
};class RandomName
{std::vector<FirstName *> m_pool;
public:RandomName();std::string GetName();void Release(std::string _name); //还名字void LoadFile(); //通过文件来构造virtual ~RandomName();
};
2.8.2 取名字
static default_random_engine rand_engine(time(NULL));std::string RandomName::GetName()
{//取姓auto num = rand_engine() % m_pool.size();auto first = m_pool[num]->m_first;//取名auto last = m_pool[num]->m_last_list[rand_engine() % m_pool[num]->m_last_list.size()];//特殊情况:若本姓的所有名都取完了,把姓删掉if (m_pool[num]->m_last_list.size() <= 0){delete m_pool[num];m_pool.erase(m_pool.begin() + num);}return first + " " + last;
}
2.8.3 还名字
void RandomName::Release(std::string _name)
{//分割名字,得到姓和名auto space_pos = _name.find(" ", 0);auto first = _name.substr(0, space_pos);auto last = _name.substr(space_pos + 1, _name.size() - space_pos - 1);bool found = false;for (auto first_name : m_pool){if (first_name->m_first == first) // 先找姓{found = true;first_name->m_last_list.push_back(last); // 把名字追加到后面break;}}if (false == found) //如果没找到姓,也就是这个姓的名字用完了,当前的名字是第一个归还的{auto first_name = new FirstName(); //创建一个新的姓first_name->m_last_list.push_back(last); // 添加名字m_pool.push_back(first_name);}
}
2.8.4 读取文件组建姓名的线性表
void RandomName::LoadFile()
{ifstream first("random_first.txt");ifstream last("random_last.txt");//读取所有名字组成一个线性表string last_name;vector<string> tmp;while (getline(last, last_name)){tmp.push_back(last_name);}//读取所有姓,创建姓名池节点,拷贝名字组成的线性表string first_name;while (getline(first, first_name)){auto first_name_list = new FirstName();first_name_list->m_first = first_name;first_name_list->m_last_list = tmp;m_pool.push_back(first_name_list);}
}
3 架构回顾
相关文章:

C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发004:游戏核心消息处理 - 玩家类的实现
文章目录 0 代码仓库1 需求2 AOI设计2.1 AOI算法简介2.2 AOI数据结构及实现2.2.1 玩家2.2.2 网格对象2.2.3 游戏世界矩形2.2.4 获取周围玩家的实现2.2.5 代码测试 2.3 GameRole结合AOI创建玩家2.3.1 创建游戏世界全局对象-GameRole继承AOIWorld的Player2.3.2 把玩家到游戏世界的…...
Python Selenium元素定位方法详解
引言 在Web自动化测试中,元素定位是一项非常重要的技术。Python Selenium提供了各种元素定位方法,可以帮助我们定位页面上的元素并与之交互。本文将详细介绍Python Selenium中常用的元素定位方法,并提供实例代码。 1. ID定位 ID是元素在HT…...
分布式事务,你了解多少?(上)
本文主要是讲述分布式事务的理论及常用的技术方案,主要源自各类学习和工作总结,如有不妥之处,还望指正。分布式事务的其他基础请自行查阅资料。 一、分布式事务产生的原因 分布式事务的产生,源自互联网、电商等的发展,…...

ClickHouse主键索引最佳实践
在本文中,我们将深入研究ClickHouse索引。我们将对此进行详细说明和讨论: ClickHouse的索引与传统的关系数据库有何不同ClickHouse是怎样构建和使用主键稀疏索引的ClickHouse索引的最佳实践 您可以选择在自己的机器上执行本文给出的所有Clickhouse SQL…...
Flink 基础 -- 应用开发(项目配置)
1、概述 本节中的指南将向您展示如何通过流行的构建工具(Maven, Gradle)配置项目,添加必要的依赖项(即连接器和格式,测试),并涵盖一些高级配置主题。 每个Flink应用程序都依赖于一组Flink库。至少,应用程序依赖于Flink api&…...
空间曲面@常见曲面方程
文章目录 曲面的基本问题特殊曲面球面方程球的标准形方程一般形方程例 柱面柱面方程不同维度下同方程的图形常见柱面方程 旋转曲面旋转曲面的方程旋转情况分类以yOz上的曲线绕 z z z轴旋转为例 旋转曲面的方程常见旋转曲面方程 锥面其他曲面 曲面的基本问题 根据曲面(点的几何…...
unity 接收和发送Udp消息
因为需要用到unity和其他的程序交互,其他程序可以提供Udp消息,因此找了合适的相互连接方法。这里直接上代码。 工具类: using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; u…...

机器学习股票大数据量化分析与预测系统 - python 计算机竞赛
文章目录 0 前言1 课题背景2 实现效果UI界面设计web预测界面RSRS选股界面 3 软件架构4 工具介绍Flask框架MySQL数据库LSTM 5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 机器学习股票大数据量化分析与预测系统 该项目较为新颖&am…...
架构描述语言(ADL)
1.架构描述语言(ADL) 架构描述语言(Architecture Description Language, ADL)是一种为明确说明软件系统的概念架构和对这些概念架构建模提供功能的语言。 2.ADL基本构成要素 ADL即架构描述语言,其基本构成要素包括:…...

GZ038 物联网应用开发赛题第2套
2023年全国职业院校技能大赛 高职组 物联网应用开发 任 务 书 (第2套卷) 工位号:______________ 第一部分 竞赛须知 一、竞赛要求 1、正确使用工具,操作安全规范; 2、竞赛过程中如有异议,可向现场考评人员反映,不得扰乱赛场秩序; 3、遵守赛场纪律,尊重考评人员,…...

Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍
Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍 文章目录 Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍一、前置原则二、一切皆组合2.1 一切皆组合2.2 垂直组合2.2.1 第一种:通过嵌入接口构建接口2.2.2 第二种:通过嵌入接…...

Vue3全局共享数据
目录 1,Vuex2,provide & inject2,global state4,Pinia5,对比 1,Vuex vue2 的官方状态管理器,vue3 也是可以用的,需要使用 4.x 版本。 相对于 vuex3.x,有两个重要变…...

openai自定义API操作 API 返回值说明
custom-自定义API操作 openai.custom 公共参数 名称类型必须描述keyString是调用key(获取测试key)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cacheStrin…...

jsp基本表格和简单算法表格
基本表格; <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd…...

在线存储系统源码 网盘网站源码 云盘系统源码
Cloudreve云盘系统源码-支持本地储存和对象储存,界面美观 云盘系统安装教程 测试环境:PHP7.1 MYSQL5.6 Apache 上传源码到根目录 安装程序: 浏览器数据 http://localhost/CloudreveInstallerlocalhost更换成你的网址 安装完毕 记住系统默认的账号密码 温馨提示:如果默认…...

线性代数(六)| 二次型 标准型转换 正定二次型 正定矩阵
文章目录 1. 二次型化为标准型1.1 正交变换法1.2 配方法 2 . 正定二次型与正定矩阵 1. 二次型化为标准型 和第五章有什么样的联系 首先上一章我们说过对于对称矩阵,一定存在一个正交矩阵Q,使得$Q^{-1}AQB $ B为对角矩阵 那么这一章中,我们…...

Kotlin系列之注解详解
目录 注解:file:JvmName 注解:JvmField 注解:JvmOverloads 注解:JvmStatic 注解:JvmMultifileClass 注解:JvmSynthetic 注解:file:JvmName file:JvmName(“XXX”) 放在类的最顶层&#x…...

Go 面向对象,多态,基本数据类型
程序功能解读 第一行为可执行程序的包名,所有的Go源文件头部必须有一个包生命语句,Go通过包名来管理命名空间。 第三行import是引用外部包的说明 func关键字声明定义一个函数,如果是main则代表是Go程序入口函数 Go源码特征解读 源程序以.g…...
使用 Python修改JSON 文件中对应键值
文章目录 前言代码分析 前言 在日常的数据处理工作中,经常需要对 JSON 文件进行读取和修改。在 Python 中,处理 JSON 文件非常方便。本文将通过一个简单的示例程序来演示如何读取和修改 JSON 文件。 代码分析 首先,需要导入 json 和 os 模块…...

【Rust日报】2023-11-08 RustyVault -- 基于 rust 的现代秘密管理系统
RustyVault -- 基于 rust 的现代秘密管理系统 RustyVault 是一个用 Rust 编写的现代秘密管理系统。RustyVault 提供多种功能,支持多种场景,包括安全存储、云身份管理、秘密管理、Kubernetes 集成、PKI 基础设施、密码计算、传统密钥管理等。RustyVault 可…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...