mangos分析
来源:互联网 发布:淘宝开个店铺多少钱 编辑:程序博客网 时间:2024/05/21 07:55
mangos
srp6
sha
分类: Mangos代码阅读用的是srp6算法加密的
1.客户端先发送AUTH_LOGON_CHALLENGE消息,其中主要含有用户名,客户端版本号
2. 服务端接受到消息后,首先进行以下check
1)该ip是否被封,若封发相应错误
2)查看是否有该账户,若无发相应错误
3)查看最后一次登陆ip与账户是否绑定
若绑定 1>当前ip与last ip相同则ok 2>不同则发相应错误
若不绑定也ok
4)查看帐号是否被封,若被封发相应错误
5)获取用户名,开始SRP6计算
// multiply with 2, bytes are stored as hexstring
if(databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2)
else
{
}
/// Make the SRP6 calculation from hash in dB
void AuthSocket::_SetVSFields(const std::string& rI)
{
}
小结一下
在服务端计算s,v
s为32个字节的随机数
v= g^x mod N
s,v存到数据库
接着计算B (服务端公钥)
B = ((v*3) + gmod) % N;
gmod = g^b mode N
b 为19个字节的随机数
向客户端发送B,g,N,s
在客户端收到B,g,N,s后
计算A 客户端公钥
A = g^a mode N
a为19为随机数
计算x
x = sha(s,I)
I = sha("username:password")
计算u
u=sha(A,B)// 公钥 (服务公钥,客户公钥)
计算S
S = (B - g^x*3)^(a+u*x)
计算K
S为32位,K为40位
是 sha(s奇部分)20位, sha(s偶部分)20位的奇偶交错组合
计算M,服务端也将有一套算法试图计算这个值,若于之相同则通过验证
t3
t4 = sha(username)
M = sha(t3,t4,s,A,B,K)
向服务端发送A,M
服务端接受到消息后
1)检查客户端版本,不支持的版本则报错
2) SRP6验证
计算S = (A * (v^u mode N ))^b mode N
u = sha(A,B)
v= g^x mod N
x = sha(s,db中存的sha(username:password)倒序)
//对应客户端S = (B - g^x*3)^(a + u*x)
同样计算K = Interleave(S),
M = sha(t3,t4,s,A,B,K)
与客户端传来的M比较,相同则验证成功
总结
关键在于服务端,客户端各自计算S的公式个不同
虽然公钥部分服务端用A,b, 客户端用B,a 但其计算结果是相同的
x这个私钥很好地得到了隐藏
服务端传B,g,N,s
客户端传A,M
要从M获取私钥x难比登天,所以即使拦截了所有客户端服务端的会话也无法破解密码
Mangos之SMSG_COMPRESSED_UPDATE_OBJECT消息
(2011-03-09 14:50:50)压缩算法
登陆游戏
封装
数据量
加载
it
分类: Mangos代码阅读加载玩家信息
Player *pCurrChar = new Player(this);
pCurrChar->LoadFromDB(GUID_LOPART(playerGuid), holder)
这两行将加载所有玩家信息,包括地图块
然后将向地图块中添加玩家
在该行执行过程中会向客户端发送玩家初始化数据
初始化数据量比较大,包括玩家属性,位置,物品,装备等等。
所以需要压缩
UpdateData类用于封装这样的大数据包,其中包含压缩算法(调用了zlib)
bool UpdateData::BuildPacket(WorldPacket *packet)
{
}
Mangos之异步数据库查询
(2011-01-28 19:55:42)mangos
异步sql查询
callback框架
分类: Mangos代码阅读为什么需要异步数据库查询?
来看一下如果两个执行顺序:
顺序1:
执行sql语句1;
对应sql语句1结果执行的动作;
执行sql语句2;
对应sql语句2结果执行的动作;
。。。。。。。。。。。
顺序2:
在线程1中
执行sql语句1;
执行sql语句2;
.........
在线程2中
添加sql语句1到线程1;
添加sql语句2到线程1;
.......... // 线程循环
对应sql语句2结果执行的动作; //次序可以是随机的,只要sql语句结果返回就对其做相应的动作
对应sql语句1结果执行的动作;
可见,顺序2有一下优点
性能更
高类似的操作集中执行
响应更快
顺序1中执行一次sql语句后紧跟着相应的处理动作,如果当前sql语句耗时很长,下一个sql语句耗时又很短,那么下一个耗时不长的sql语句的相应方法就必须等待当前sql语句及相应的处理动作执行完后才能执行.
在Mangos中,对数据库characters的操作就使用了异步sql,大概是因为mangos对于该数据库的操作比较平凡且响应速度要求比较高,设想上千个玩家登陆服务器,每时每刻的变化都要保存到characters数据库中,并且其他玩家变化及时地反映到游戏世界中。
先来看一下mangos中异步sql的使用方法:
void WorldSession::HandleCharEnumOpcode( WorldPacket & )
{
//get all the data necessary for loading all characters (along with their pets) on the account
//第一个参数&chrHanler是包含一组回调方法的类对象
//第二个参数则是该类中某个回调方法,这个回调方法以sql语句执行的结果QueryResult作为参数
//第三个参数用作回调方法的第二个参数
//第四个参数是sql语句的format形式
//下面的多个参数是sql语句format中用到的变量
CharacterDatabase.AsyncPQuery(&chrHandler, &CharacterHandler::HandleCharEnumCallback, GetAccountId(),"sql format", PET_SAVE_AS_CURRENT, GetAccountId());
}
来看一下这个专门用来处理角色信息的handler类:
class CharacterHandler
{
} chrHandler;
具体回调运行过程在WorldSession中:
void WorldSession::HandleCharEnum(QueryResult * result)
{
}
总体上的调用流程是上这样的:
在游戏主线程的循环体中会调用世界对象sWorld的update方法,其中做了下面这些事
来看一下结果队列的update方法
void SqlResultQueue::Update()
{
}
CharacterDatabase中的异步sql实现:
上面大致介绍了异步sql的执行流程,现在看一下它是如何实现的
1.异步sql执行线程
CharacterDatabase在初始化时会开启一个专门用于执行异步sql的线程
void DatabaseMysql::InitDelayThread()
{
(this);
}
下面是这个线程的类图设计:
类Thread是对ACE线程操作的wrapper,它包含了启动,关闭线程等功能,其构造函数为要开启的Thread赋予线程执行体并开启线程,具体实现这里就不讨论了,都是ACE Developemnt Guide上找的到的。
关键是SqlDelayThread,线程的方法体。
成员m_dbEngine是DatabaseMysql的实例,DatabaseMysql对mysqlapi做了封装,其中除提供基本sql操作功能外,还包含了像AsyncQuery,transaction相关的方法,这里需要这个引用是为了能执行sql基本操作。
成员m_sqlQueue是个线程安全的sql操作队列,sql语句被封装成SqlOperation之后放入该队列。
方法 Delay(SqlOperation* sql)将sql操作置入操作队列中
线程方法体定义:
void SqlDelayThread::run()
{
}
SqlOperation的类图设计
SqlOperation是抽象类,具体操作如查询操作SqlQuery实现了它。
成员m_callback是回调函数接口
m_queue则是sql语句执行完毕后生成的结果队列,同样是线程安全的
前面已经提到过,显而易见,sql执行线程中主要用到SqlOperation的Execute方法。
void SqlQuery::Execute(Database *db)
{
}
前文不知不觉中多次用了两个概念: 回调对象,结果队列
其实回调对象是真正的精华所在,而结果队列也只是建立在回调对象之上的东东,其设置和使用也值得一提
看一下SqlQuery的构造函数
后面二个对象分别就是回调对象,结果队列。
回调对象内容比较多,先来看看结果队列:
LockedQueue是个模板类,用于多线程的queue,SqlResultQueue继承LockedQueue以IQueryCallback为容器对象,ACE_Thread_Mutex为锁类型的模板实例,它还包含一个Update方法。
void SqlResultQueue::Update()
{
}
之前提到过主线程中某处调用了UpdateResultQueue();
对就是在这里调用SqlResultQueue::Update方法
也许聪明的你会发现,主线程调用UpdateResultQueue,往ResultQueue放回调对象的是sqlDelayThread,
多个线程共用了ResultQueue,那么另一个线程(非主线程和sqlDelayThread)也许也有需要使用异步sql,而sqlDelayThread是否可以被共享呢?
有了这样的需求,DataBaseMysql有如下的设计
Database为抽象类,DatabaseMysql用mysql api实现了Database
注意成员m_queryQueues是个线程指针和结果队列SqlResultQueue,也即是说DatabaseMysql保存来自各个线程的结果队列,而每个sqlOperation也知道自己所属的结果队列,如
SqlQuery(const char *sql, MaNGOS::IQueryCallback * callback, SqlResultQueue * queue) //第三个参数
所以SqlOperation执行结果会被放入它所属的结果队列中,而sqlDelayThread不区分结果队列地执行每个SqlOperation。
这样就构成了sqlDelayThread为多个线程所共享,每个线程负责创建自己的结果队列,在创建SqlOperation时指定自己的结果队列,并在自己线程的适当地方调用UpdateResultQueue。
看一下World世界对象对结果队列的操作。
void World::InitResultQueue()
{
}
void Database::SetResultQueue(SqlResultQueue * queue)
{
}
回过头来看一下AsyncQuery这个方法
template<class Class, typename ParamType1>bool Database::AsyncQuery(Class *object, void (Class::*method)(QueryResult*, ParamType1), ParamType1 param1, const char *sql)
方法体中使用了预定义宏
#define ASYNC_QUERY_BODY(sql, queue_itr) \
回调对象是最为精髓的地方,因为它能将一个包含一些回调方法的类(它继承任何其他类)封装为回调接口MaNGOS::IQueryCallback * callback 的实例。
template<class Class, typename ParamType1>
bool
Database::AsyncQuery(Class *object, void (Class::*method)(QueryResult*, ParamType1), ParamType1 param1, const char *sql)
{
}
AsyncQuery的重载方法是个template方法
第一个参数根据模板的不同而不同, 但使用是不用指定<class Class>,第一个参数直接放入某个类的执政,
c++会在编译时生成用这个类的执政作为第一个参数的方法代码。
第二个参数是相应第一个参数指定的类型域的某个方法,它以QueryResult作为第一个参数,后面可以是多个模板参数ParamType1,ParamType2,也可以没有模板参数
这里列出的只有ParamType1,这是由于本文开头例子用的是
因为CharacterHandler::HandleCharEnumCallback需要ParamType1这个参数来找到属于自己的会话
void HandleCharEnumCallback(QueryResult * result, uint32 account)
当有如上两条语句是,c++根据模板类自动生成相应的代码。
下面解读AsyncQuery中的这条语句:
m_threadBody->Delay(new SqlQuery(sql, new MaNGOS::QueryCallback<Class, ParamType1>(object, method, (QueryResult*)NULL, param1), itr->second));
关键是new SqlQuery的第二个参数
看一下其原型
第二个参数是IQueryCallback接口
AsyncQuery中为它赋的是,所以重点解释一下这条语句
new MaNGOS::QueryCallback<Class, ParamType1>(object, method, (QueryResult*)NULL, param1)
为方便,再看一下相应的AsyncQuery原型
template<class Class, typename ParamType1>
bool Database::AsyncQuery(Class *object, void (Class::*method)(QueryResult*, ParamType1), ParamType1 param1, const char *sql)
放入MaNGOS::QueryCallback的第一个参数object
类型为Class *object
值为&chrHandler //class CharacterHandler的对象
具体类型为CharacterHandler
放入MaNGOS::QueryCallback的第二个参数method
类型是void (Class::*method)(QueryResult*, ParamType1)
值为 &CharacterHandler::HandleCharEnumCallback
具体类型为HandleCharEnumCallback的原型:
放入MaNGOS::QueryCallback的第三个参数NULL
它不是模板参数,只有具体类型为QueryResult,值为NULL
放入MaNGOS::QueryCallback的第四个参数param1
类型ParamType1 param1,值为GetAccountId()返回结果,具体类型是uint32
看一下QueryCallback 的原型:
template < class Class, typename ParamType1 >
class QueryCallback < Class, ParamType1 > :public _IQueryCallback< _Callback < Class, QueryResult*, ParamType1 > >{
};
那上面的模板参数类型与原型参数类型比对
第一个参数类型:
Class *object
第二个参数类型:
void (Class::*method)(QueryResult*, ParamType1)
第三个是具体参数类型:
QueryResult* result
第四个参数类型:
ParamType1 param1
只有第二个参数类型需要研究一下, _Callback 的定义
template < class Class, typename ParamType1, typename ParamType2 >
class _Callback < Class, ParamType1, ParamType2 >
{
};
_Callback < Class, QueryResult*, ParamType1 >::Method
以实例化模板中类型
void (Class::*Method)(ParamType1, ParamType2)
所以Method 就是 void (Class::*method)(QueryResult*, ParamType1)
这里复杂性在于它用一个模板参数实例化另一个模板
讲的有点乱,画个图说明一下:
首先模板实例化的是QueryCallback
Class为CharacterHandler
ParamType1为uint32
QueryCallback中又模板实例化_Callback
这样CharacterHandler与_Callback进行了绑定,它是能真正执行代码的类
void _Execute(){(m_object->*m_method)(m_param1,m_param2);}//执行callback方法
QueryCallback继承_IqueryCallback
_IqueryCallback也需要模板实例化,用的模板参数是刚才实例化的callback QC1
_IqueryCallback继承 QC1 和 接口IQueryCallback
其中实现了IQueryCallback的方法
void Execute()(CB:_Execute();) //CB为模板参数,被赋值QC1
void SetResult(QueryResult* result){CB::m_param1 = result;}
QueryResult* GetResult(){return CB::m_param1;}
这样一来QueryCallback就等同于用CharacterHandler的方法实现了IQueryCallback
AsyncPQuery方法体中
new MaNGOS::QueryCallback<Class, ParamType1>(object, method)这个语句后,就等同于new了一个封装了CharacterHandler的IQueryCallback接口的实例。
在调用时可利用多态的特性调用每个IQueryCallback所绑定的Handler方法了。
写这个真累,虽然知道是写给自己看的。。。。。。。。。。。
Mangos服务器会话WorldSession
(2011-01-24 08:57:32)worldsession
mangos
分类: Mangos代码阅读所有的游戏中的操作如角色移动,释放技能等都是要通过在会话中消息传递来进行。
当客户端通过服务器验证后,便与之建立起会话。下面是套接字handler: WorldSocket中HandleAuthSession方法的部分代码:
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)
{
游戏主线程会不断地调用sWorld的UpdateSessions()
void World::UpdateSessions( uint32 diff )
{
}
看一下WorldSession的Update()方法
bool WorldSession::Update(uint32 )
{