38.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-解码器类的优化调试

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

如果看不懂、不知道现在做的什么,那就跟着做完看效果

内容参考于:易道云信息技术研究院VIP课

上一个内容:37.解码器细化类的实现

码云地址(master 分支):https://gitee.com/dye_your_fingers/titan

码云版本号:ccc3dd3b76e4f0fd13f8fc86cb062606004db2bd

代码下载地址,在 titan 目录下,文件名为:titan-解码器类的优化调试.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk升级版.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

以 37.解码器细化类的实现 它的代码为基础进行修改

解码器这个东西,具体还要看游戏中解析约定的内容多不多,如果不多这个解码的功能就亏了

效果图:

把下方内容复制出去,加个头加个尾就是一个完整的结构体了,比如class aaa{复制的内容}这样

比如

class a{
GBYTE none;//0
GINT none;//0
GINT none;//0
GUTF16 none;//[4399]
GUTF16 none;//[1,1;12,21031;13,21039;15,;17,;18,;19,;24,122011;26,112011;27,132011;28,152011;29,142011;30,;35,1;36,1;37,1;38,1;39,;41,;42,289041;44,13352902839408;45,TY_Sword_0001;46,;53,22131;54,22132;55,22140;56,10400000;57,;58,;59,101011;60,;61,0;62,;63,0;64,0;65,296018;66,16777215;]
GINT none;//0
GINT64 none;//64d
}

NetClient.cpp文件的修改:

#include "pch.h"
#include "NetClient.h"
#include "extern_all.h"bool NetClient::login(const char* Id, const char* Pass)
{const int bufflen = sizeof(DATA_LOGIN) + 1;char buff[bufflen];DATA_LOGIN data;// 有些操作系统这样写会报错,因为内存不对齐,现在Windows下没事//PDATALOGIN _data = (PDATALOGIN)(buff + 1);// 这样写就能解决内存对齐问题PDATALOGIN _data =&data;int len = strlen(Id);memcpy(_data->Id, Id, len);len = strlen(Pass);memcpy(_data->Pass, Pass, len);memcpy(buff+1, _data, sizeof(DATA_LOGIN));buff[0] = I_LOGIN;WinSock->OnSend(buff, sizeof(buff));return true;
}void NetClient::loginfailed(int code)
{CString txt;if (code == 51001) {txt = L"登陆失败,易道云通行证不存在!";}else if (code == 51002) {txt = L"登录失败,密码错误!";}else txt = L"未定义错误!";#ifdef  Anlyanly->SendData(TTYPE::I_LOG, 0, txt.GetBuffer(), txt.GetLength()*2);
#endif
}void NetClient::loginok(ROLE_DATA* roles, int count)
{// 入参 roles 存在内存泄漏CString txt;CString tmp;txt.Format(L"游戏登录成功!角色数量[%d]\r\n", count);for (int i = 0; i < count; i++){tmp.Format(L"byte=%d\r\n", roles[i].byte.value());txt += tmp;tmp.Format(L"un=%d\r\n", roles[i].un.value());txt += tmp;tmp.Format(L"un1=%d\r\n", roles[i].un1.value());txt += tmp;tmp.Format(L"name=%s\r\n", roles[i].name.value());txt += tmp;tmp.Format(L"infos=%s\r\n", roles[i].infos.value());txt += tmp;tmp.Format(L"un2=%d\r\n", roles[i].un2.value());txt += tmp;tmp.Format(L"un3=%d\r\n", roles[i].un3.value());txt += tmp;}AfxMessageBox(txt);
}

GameWinSock.h文件的修改:新加 AnlyBuff函数

#pragma once#define I_LOGIN 0x2 // 登录#define S_LOGINFAIL 0x3 // 登录失败的返回数据包的头
#define S_LOGINOK 0x4 // 登录成功的返回数据包的头class GameWinSock
{typedef bool(GameWinSock::* PROC)(char*, unsigned);
public:unsigned* vatble;// 虚函数表unsigned un[17];unsigned RecvPoint; // 游戏recv之后调用处理一个数据包函数时的eax,这里偏移是0x48
public:static PROC _OnConnect;static PROC _OnSend;static PROC _OnRecv;bool OnConnect(char* ip, unsigned port);bool OnSend(char* buff, unsigned len);bool OnRecving(char* buff, unsigned len);bool OnRecv(char* buff, unsigned len);void Init();// end为了避免超长解读void AnlyBuff(char* start, char* end, char index = 0);
};

GameWinSock.cpp文件的修改:新加 AnlyBuff函数,修改了 OnloginOk函数

#include "pch.h"
#include "GameWinSock.h"
#include "extern_all.h"
#include "NetClass.h"
#include "EnCode.h"typedef bool(* DealProc)(char*&, unsigned&);DealProc SendDealProc[0x100];
DealProc RecvDealProc[0x100];GameWinSock::PROC GameWinSock::_OnConnect{};
GameWinSock::PROC GameWinSock::_OnSend{};
GameWinSock::PROC GameWinSock::_OnRecv{};bool DeafaultDeal(char*&, unsigned&) { return true; }// 登录数据包的处理
bool Onlogin(char *& buff, unsigned& len) { /* 修改账号密码len = sizeof(DATA_LOGIN) + 1;buff = new char[len];DATA_LOGIN data;PDATALOGIN _data = &data;buff[0] = 0x2;CStringA _id = "";// 补充账号CStringA _pass = "";// 补充密码memcpy(_data->Id, _id.GetBuffer(), _id.GetLength());memcpy(_data->Pass, _pass.GetBuffer(), _pass.GetLength());memcpy(buff + 1, _data, len - 1);*//* 监控登录数据PDATALOGIN _data = (PDATALOGIN)buff;CStringA _id = _data->Id;_data = (PDATALOGIN)(buff + _data->lenId - 0x10);CStringA _pass = _data->Pass;CStringA _tmp;// 请求登录 账号[% s]密码[% s] 这个内容别人在逆向的时候就会看到// 所以这种东西需要自己搞个编码来代替它_tmp.Format("请求登录 账号[%s]密码[%s]", _id, _pass);
#ifdef  Anlyanly->SendData(TTYPE::I_DIS, 1, _tmp.GetBuffer(), _tmp.GetAllocLength());
#endif*//*返回false,游戏无法发送数据包原因看调用此此函数的位置 OnSend 函数(if (SendDealProc[buff[0]]((buff + 1), len - 1)))*/return true;
}bool Onloginfailed(char*&buff, unsigned& len) { int* code = (int*)&buff[1];Client->loginfailed(code[0]);return true; 
}bool OnloginOk(char*& buff, unsigned& len) {PDATALOGINOK _p = (PDATALOGINOK)&buff[1];ROLE_DATA* roleDatas = nullptr;if (_p->RoleCount > 0) {char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);WinSock->AnlyBuff(buffStart, buff + len);roleDatas = new ROLE_DATA[_p->RoleCount];for (int i = 0; i < _p->RoleCount; i++){roleDatas[i].byte = buffStart;roleDatas[i].un = buffStart;roleDatas[i].un1 = buffStart;roleDatas[i].name = buffStart;roleDatas[i].infos = buffStart;roleDatas[i].un2 = buffStart;roleDatas[i].un3 = buffStart;}Client->loginok(roleDatas, _p->RoleCount);}return true;
}// 这个函数拦截了游戏的连接
bool GameWinSock::OnConnect(char* ip, unsigned port)
{
#ifdef  Anly// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个// 所以 11 乘以2,然后再加2 anly->SendData(TTYPE::I_LOG, 0, L"服务器正在连接。。。", 24);
#endif// this是ecx,HOOK的点已经有ecx了WinSock = this;bool b = (this->*_OnConnect)(ip, port);// 下方注释的代码时为了防止多次注入,导致虚函数地址不恢复问题导致死循环,通过一次性HOOK也能解决/*unsigned* vtable = (unsigned*)this;vtable = (unsigned*)vtable[0];union {unsigned value;bool(GameWinSock::* _proc)(char*, unsigned);} vproc;vproc._proc = _OnConnect;DWORD oldPro, backProc;VirtualProtect(vtable, 0x10x00, PAGE_EXECUTE_READWRITE, &oldPro);vtable[0x34 / 4] = vproc.value;VirtualProtect(vtable, 0x10x00, oldPro, &backProc);*/return b;
}bool GameWinSock::OnSend(char* buff, unsigned len)
{/*这里就可以监控游戏发送的数据了*/#ifdef  Anlyanly->SendData(TTYPE::I_SEND, buff[0], buff, len);
#endif/*数据包的头只有一字节所以它的取值范围就是0x0-0xFF*/if (SendDealProc[buff[0]]((buff), len)) {// 执行失败不让游戏发送数据包return (this->*_OnSend)(buff, len);}else {// 发送失败屏蔽消息return true;// 屏蔽消息}}bool GameWinSock::OnRecving(char* buff, unsigned len)
{// MessageBoxA(0, "11111111111111", "0", MB_OK);/*监控游戏接收的数据包*/
#ifdef  Anlyanly->SendData(TTYPE::I_RECV, buff[0], buff, len);
#endifreturn RecvDealProc[buff[0]](buff, len);
}bool GameWinSock::OnRecv(char* buff, unsigned len)
{
//#ifdef  Anly
//	anly->SendData(1, buff, len);
//#endifreturn (this->*_OnRecv)(buff, len);
}void GameWinSock::Init()
{for (int i = 0; i < 0x100; i++) {SendDealProc[i] = &DeafaultDeal;RecvDealProc[i] = &DeafaultDeal;}// 注册登录数据包处理函数// SendDealProc[I_LOGIN] = &Onlogin;// 注册数据登录失败数据包处理函数RecvDealProc[S_LOGINFAIL] = &Onloginfailed;RecvDealProc[S_LOGINOK] = &OnloginOk;
}// 它会生成一个结构体,详情看效果图
void GameWinSock::AnlyBuff(char* start, char* end, char index)
{
#ifdef  AnlyCStringA txt;CStringA tmp;CString utmp;EnCode _coder;GBYTE* _bytecoder;GSHORT* _shortcoder;GINT* _intcoder;GFLOAT* _floatcoder;GDOUBLE* _doublecoder;GCHAR* _asccoder;GUTF16* _utfcoder;GINT64* _int64coder;CStringA _opname = data_desc[_coder.index][_coder.op].name;while (start < end) {_coder.Init(start, index);// _opname.MakeLower()是变为小写字母,会影响 _opname它的值// 所以又写了一边 data_desc[_coder.index][_coder.op].nametmp.Format("%s %s;//", data_desc[_coder.index][_coder.op].name, _opname.MakeLower());txt = txt + tmp;if (_coder.index == 0) {switch (_coder.op){case 1:_shortcoder = (GSHORT*)&_coder;tmp.Format("%d\r\n", _shortcoder->value());txt = txt + tmp;break;case 2:_intcoder = (GINT*)&_coder;tmp.Format("%d\r\n", _intcoder->value());txt = txt + tmp;break;case 4:_floatcoder = (GFLOAT*)&_coder;tmp.Format("%d\r\n", _floatcoder->value());txt = txt + tmp;break;case 6:_bytecoder = (GBYTE*)&_coder;tmp.Format("%d\r\n", _bytecoder->value());txt = txt + tmp;break;case 7:_utfcoder = (GUTF16*)&_coder;utmp.Format(L"[%s]\r\n", _utfcoder->value());tmp = utmp;txt = txt + tmp;break;case 3:case 5:case 8:_int64coder = (GINT64*)&_coder;tmp.Format("%.i64d\r\n", _int64coder->value());txt = txt + tmp;break;default:break;}}}// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个// 所以 11 乘以2,然后再加2 anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif//		char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);
//		char* buffEnd = buff + len;
//		while (buffStart < buffEnd) {
//			/*
//				由于 EnCode 的入参带了& 所以可以在EnCode里直接对buffStart或len进行操作
//				也就是说只要入参带了&在函数里的操作会影响它原本的值
//				比如 
//				char*a = 0;
//				aaa(a)
//				aaa(char*&AA){
//					AA = new char[10]; // 它就会把a的值改成new char[10]
//				}
//			*/
//			EnCode* _coder = new EnCode(buffStart, len);
//			tmp.Format("%s:",data_desc[_coder->index][_coder->op].name);
//			txt = txt + tmp;
//
//			if (_coder->op == 0x7) {
//				utmp = (wchar_t*)_coder->pointer;
//				tmp = utmp;
//				txt = txt + tmp;
//			}else
//			if (_coder->op == 0x3) {
//				tmp.Format("[%f]", _coder->fval);
//				txt = txt + tmp;
//			}
//			else {
//				tmp.Format("[%d]", _coder->val);
//				txt = txt + tmp;
//			}
//		}
//
//#ifdef  Anly
//		// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个
//		// 所以 11 乘以2,然后再加2 
//		anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
//#endif
}

extern_all.cpp文件的修改:修改了 init_datadesc函数

/*此文件是用来存放全局变量、全局函数(通用函数)
*/
#include "pch.h"
#include "extern_all.h"
GameWinSock* WinSock = nullptr;
GameProc* PGameProc = nullptr;
NetClient* Client = nullptr;
#ifdef Anly
CAnly* anly = nullptr;
#endifDATA_DESC data_desc[2][9]{};void init_datadesc() {data_desc[0][0] = { "none", 0 };data_desc[0][1] = { "GSHORT", 2 };data_desc[0][2] = { "GINT", 4 };data_desc[0][3] = { "GINT64", 8 };data_desc[0][4] = { "GFLOAT", 4 };data_desc[0][5] = { "GINT64", 8 };data_desc[0][6] = { "GBYTE", 1 };data_desc[0][7] = { "GUTF16", 4 };data_desc[0][8] = { "GINT64", 8 };data_desc[1][0] = { "none", 0 };data_desc[1][1] = { "GSHORT", 2 };data_desc[1][2] = { "GINT", 4 };data_desc[1][3] = { "GINT64", 8 };data_desc[1][4] = { "GFLOAT", 4 };data_desc[1][5] = { "GINT64", 8 };data_desc[1][6] = { "GCHAR", 4 };data_desc[1][7] = { "GUTF16", 4 };data_desc[1][8] = { "GINT64", 8 };
}void InitClassProc(LPVOID proc_addr, unsigned value)
{unsigned* writer = (unsigned*)proc_addr;writer[0] = value;
}

EnCode.h文件的修改:实现 value、operator=函数(只有EnCode类实现了,其余类都是声明它,实现用的是父类的也就是EnCode类的实现)

#include "pch.h"
#include "EnCode.h"
#include "extern_all.h"int EnCode::Init(char*& buff, char EnIndex)
{index = EnIndex;op = buff[0];int len = data_desc[index][op].lenth;/*比如 07 0A 00 00 00 34 00 33 00 39 00 39 00 00 00buff + 1 的结果是 0A 00 00 00 34 00 33 00 39 00 39 00 00 00len是 07 所代表的类型的长度,也就是0A 00 00 00 这个东西memcpy(dataPool, buff + 1, len);也就是把 0A 00 00 00 复制到 dataPool里然后从下方的if ((op == 0x7))得到它的字符串*/memcpy(dataPool, buff + 1, len);buff = buff + 1 + len;// 下一个结构if (index == 0) {if ((op == 0x7)) {if (pointer)delete[]pointer;/*dataPool 与 lenth是一个联合体,联合体的特性就是所有变量共用一个内存,所以 dataPool 的值就是lenth的值从上方的注释得知现在dataPool的值是0A 00 00 00然后通过 lenth去读 0A 00 00 00结果就是十进制的 10然后创建一个10字节的空间给pointer然后在通过 memcpy(pointer, buff, lenth); 把字符串赋值给pointer*/pointer = new char[lenth];memcpy(pointer, buff, lenth);buff = buff + lenth;// 指向下一个字符串}}if (index == 1) {if ((op == 0x06) || (op == 0x7)) {if (pointer)delete[]pointer;pointer = new char[lenth];memcpy(pointer, buff, lenth);buff = buff + lenth;}}return 0;
}
/*buff是数据包_len暂时没用ExIndex是解析方式,因为分析的时候发现6有时是char类型有时是char*类型看懂此方法需要分析手动分析一次数据包(数据解析约定的数据包)然后带着数据包去看这个函数
*/
EnCode::EnCode(char*& buff, unsigned int& _len,char EnIndex)
{Init(buff,EnIndex);
}EnCode::~EnCode()
{if(pointer)delete[] pointer;
}EnCode::EnCode()
{
}EnCode& EnCode::operator=(char*& buff)
{Init(buff);return *this;
}GBYTE::operator char()
{return this->byte;
}char GBYTE::value()
{return this->byte;
}GSHORT::operator short()
{return this->stval;
}short GSHORT::value()
{return this->stval;
}GINT::operator int()
{return this->val;
}int GINT::value()
{return this->val;
}GFLOAT::operator float()
{return this->fval;
}float GFLOAT::value()
{return this->fval;
}GINT64::operator long long()
{return this->lval;
}long long GINT64::value()
{return this->lval;
}GDOUBLE::operator double()
{return this->dbval;
}double GDOUBLE::value()
{return this->dbval;
}GCHAR::operator const char*()
{return this->pointer;
}const char* GCHAR::value()
{return this->pointer;
}GUTF16::operator const wchar_t*()
{return (wchar_t*)this->pointer;
}const wchar_t* GUTF16::value()
{return (wchar_t*)this->pointer;
}

EnCode.h文件的修改:新加 operator=函数、value函数(只有G开头的类里加的)

#pragma onceclass EnCode
{
private:char un[2]{};// 这个是为了内存对齐
public:char index = 0;// op + un[3]是四字节,op与buffer是6字节,如果自动内存对齐op与buffer可能不会挨在一起char op = 0;union{char dataPool[0x8];int lenth;char byte;short stval;int val;float fval;double dbval;long long lval = 0;};char* pointer = 0;
public:EnCode();EnCode(char*& buff, unsigned int& _len, char ExIndex = 0);EnCode& operator=(char*& buff);int Init(char*& buff, char EnIndex = 0);~EnCode();
};class GBYTE :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator char();char value();
};
class GSHORT :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator short();short value();
};
class GINT :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator int();int value();
};
class GFLOAT :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator float();float value();
};
class GINT64 :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator long long();long long value();
};
class GDOUBLE :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator double();double value();
};
class GCHAR :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator const char*();const char* value();
};
class GUTF16 :public EnCode {
public:using EnCode::EnCode;using EnCode::operator=;operator const wchar_t*();const wchar_t* value();
};

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

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

相关文章

“光学行业正被量子颠覆”——行业巨头齐聚,展示量子成果

OFC是全球最大的光网络和通信盛会&#xff0c;代表一系列产品&#xff0c;从光学元件和设备到系统、测试设备、软件和特种光纤&#xff0c;代表整个供应链&#xff0c;并提供业界学习、连接、建立网络和达成交易的首要市场&#xff0c;于2024年3月24日至28日在圣地亚哥会议中心…

excel中批量插入分页符

excel中批量插入分页符&#xff0c;实现按班级打印学生名单。 1、把学生按照学号、班级排序好。 2、选择班级一列&#xff0c;点击数据-分类汇总。汇总方式选择计数&#xff0c;最后三个全部勾选。汇总结果一定要显示在数据的下发&#xff0c;如果显示在上方&#xff0c;后期…

Science Robotics 逼真面部表情的机器人

人类可以产生数千种不同的面部表情来传达无数微妙的情绪状态&#xff0c;这种能力是人类社会互动中最有效和最有效的界面之一。在 2019 年冠状病毒病流行期间&#xff0c;口罩使社交互动变得尴尬&#xff0c;因为它们掩盖了面部表情。同时&#xff0c;当摄像机打开时&#xff0…

成都市酷客焕学新媒体科技有限公司:实现品牌的更大价值!

成都市酷客焕学新媒体科技有限公司专注于短视频营销&#xff0c;深知短视频在社交媒体中的巨大影响力。该公司巧妙地将品牌信息融入富有创意和趣味性的内容中&#xff0c;使观众在轻松愉悦的氛围中接受并传播这些信息。凭借独特的创意和精准的营销策略&#xff0c;成都市酷客焕…

火车头通过关键词采集文章的原理

随着互联网信息的爆炸式增长&#xff0c;网站管理员和内容创作者需要不断更新和发布新的文章&#xff0c;以吸引更多的用户和提升网站的排名。而火车头作为一款智能文章采集工具&#xff0c;在这一过程中发挥着重要作用。本文将探讨火车头如何通过关键词采集文章&#xff0c;以…

SAP BTP云上一个JVM与DB Connection纠缠的案例

前言 最近在CF (Cloud Foundry) 云平台上遇到一个比较经典的案例。因为牵扯到JVM &#xff08;app进程&#xff09;与数据库连接两大块&#xff0c;稍有不慎&#xff0c;很容易引起不快。 在云环境下&#xff0c;有时候相互扯皮的事蛮多。如果是DB的问题&#xff0c;就会找DB…

网络爬虫框架Scrapy的入门使用

Scrapy的入门使用 Scrapy概述引擎&#xff08;Engine&#xff09;调度器&#xff08;Scheduler&#xff09;下载器&#xff08;Downloader&#xff09;SpiderItem Pipeline 基本使用安装scrapy创建项目定义Item数据模型对象创建爬虫(Spider)管道pipeline来保存数据启动爬虫 其他…

cuda python课程中“使用 Numba 的 CUDA Python 的自定义核函数和内存管理“一个大坑

总觉得自己的算法没问题&#xff0c;最终还是测试结果不符。 错误出现在一个很顺眼的位置。 解决方案 在最终测验阶段有一个看起来没问题不需要任何修改的Cell&#xff0c;就是这一句&#xff0c;看起来没什么毛病 d_histogram_out cuda.device_array_like(histogram_out)需…

Java代码混淆技术在应用程序保护中的关键作用与应用

摘要 本文探讨了代码混淆在保护Java代码安全性和知识产权方面的重要意义。通过混淆技术&#xff0c;可以有效防止代码被反编译、逆向工程或恶意篡改&#xff0c;提高代码的安全性。常见的Java代码混淆工具如IPAGuard、Allatori、DashO、Zelix KlassMaster和yGuard等&#xff0…

【数据分析面试】3.编写数据选取函数(Python)

题目 给定了一个名为 students_df 的学生数据表格 nameagefavorite_colorgradeTim Voss19red91Nicole Johnson20yellow95Elsa Williams21green82John James20blue75Catherine Jones23green93 编写一个名为 grades_colors 的函数&#xff0c;以选择仅当学生喜欢的颜色是绿色或…

YOLOv5-小知识记录(三)

0. 写在前面 本篇介绍SPP模块、FPN模块模块&#xff0c;主要也是对YOLOv5的内容的补充&#xff1a; Yolo系列算法-理论部分-YOLOv4-CSDN博客 Yolo系列算法-理论部分-YOLOv5-CSDN博客 上一篇&#xff1a; YOLOv5-小知识记录(二)-CSDN博客 1. SPP模块 Spatial Pyramid Pooling…

探索数据库--------------mysql主从复制和读写分离

目录 前言 为什么要主从复制&#xff1f; 主从复制谁复制谁&#xff1f; 数据放在什么地方&#xff1f; 一、mysql支持的复制类型 1.1STATEMENT&#xff1a;基于语句的复制 1.2ROW&#xff1a;基于行的复制 1.3MIXED&#xff1a;混合类型的复制 二、主从复制的工作过程 三个重…