标签: mangossrp6sha | 分类: 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难比登天,所以即使拦截了所有客户端服务端的会话也无法破解密码
加载玩家信息
Player *pCurrChar = new Player(this);
pCurrChar->LoadFromDB(GUID_LOPART(playerGuid), holder)
这两行将加载所有玩家信息,包括地图块
然后将向地图块中添加玩家
在该行执行过程中会向客户端发送玩家初始化数据
初始化数据量比较大,包括玩家属性,位置,物品,装备等等。
所以需要压缩
UpdateData类用于封装这样的大数据包,其中包含压缩算法(调用了zlib)
bool UpdateData::BuildPacket(WorldPacket *packet)
{
}
标签: 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方法了。
写这个真累,虽然知道是写给自己看的。。。。。。。。。。。
标签: worldsessionmangos | 分类: Mangos代码阅读 |
所有的游戏中的操作如角色移动,释放技能等都是要通过在会话中消息传递来进行。
当客户端通过服务器验证后,便与之建立起会话。下面是套接字handler: WorldSocket中HandleAuthSession方法的部分代码:
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)
{
游戏主线程会不断地调用sWorld的UpdateSessions()
void World::UpdateSessions( uint32 diff )
{
}
看一下WorldSession的Update()方法
bool WorldSession::Update(uint32 )
{
}
接受消息队列的数据包是哪来的呢? 看这里:
int WorldSocket::ProcessIncoming (WorldPacket* new_pct)
{
标签: acemangossocket反应器 | 分类: Mangos代码阅读 |
先来看一下启动监听socket的地方:
Master.cpp
Mangos用WorldSocketMgr这个类来管理socket的运作。StartNetwork中主要调用了StartReactiveIO来启动监听socket,看一下StartReactiveIO这个方法:
WorldSocketMgr::StartReactiveIO (ACE_UINT16 port, const char* address)
{
}
每个线程都有一个反应器ACE_Reactor* m_Reactor,它的具体实现是ACE_TP_Reactor(用于多线程的reactor),
线程运行部分就是调用反应器来实现的:
客户端连接产生的socket是怎样被分配到指定线程及相应的反应器的呢?
前面已经提到过,acceptor被指定用第一个线程和相应的反应器,当有客户端连接时,acceptor将创建一个svc_handler,这里是WorldSocket,然后调用WorldSocket的open方法。下面是图示
OnSocketOpen方法:
int
WorldSocketMgr::OnSocketOpen (WorldSocket* sock)
{
}
AddSocket将worldSocket添加到m_NewSockets中。m_NewSockets在线程运行体中被添加到m_Sockets中,并调用其update()方法.
{
}
Update主要调用handle_output方法
int WorldSocket::handle_output (ACE_HANDLE)
{
#ifdef MSG_NOSIGNAL
#else
#endif // MSG_NOSIGNAL
}
注意事项:
使用ace的svc_handler时注意,注册read事件之后,reactor会根据实际网络输入来处理输入事件,而write事件要用户自己触发。
先来看一下reamld工程下的Main.cpp, 有如下内容
SocketHandler,AuthSocket,ListenSocket这三个类几乎构成了reamld socket通信的全部。
ListenSocket用于监听来自客户端的连接请求,并把为每个创建的套接字传给AuthSocket
AuthSocket处理单个连接之间的消息,其实就是用来验证用户身份,返回游戏服务器地址列表等。
AuthSocket,ListenSocket都直接或间接继承自Socket这个基类,其封装了如SOCKET句柄等底层socket api的数据和函数。但整个控制流程又是怎样设计的呢? 比如accept一个连接请求,用这个连接创建的新套接字发送接收数据等等。
SocketHandler便是用于处理这整个控制流程的。它包含所有活跃的套接字,循环这些套接字select方法查看套接字的状态,如果为可读或可写则利用多态调用相应的读取和发送数据的具体方法。
在SocketHandler中,定义了以下集合
程序在需要的地方调用 SocketHandler::Add(Socket *p)将添加m_add集合,
每次调用SocketHandler::Select(struct timeval *tsel) 这个方法时,它会先将m_add中的sockets添加到
m_sockets中去并为这些新加入的sockets设置m_rfds,m_wfds,m_efds集合。
然后调用
现在可以检查某个集合是否有I/O操作,若有则调用回调函数,比如:
#ifdef HAVE_OPENSSL
#endif
也就是说当ListenSocket调用OnRead()表示需要accept一个新的连接,AuthSocket调用OnRead()表示有数据从客户端传过来了。
ListenSocket的OnRead()方法:
ListenSocket
ListenSocket是template class,它接收X template参数,X这里是AuthSocket,作为服务具体某个连接的数据交换的类。可以看到ListenSocket将接收的新连接套接字付给了X.
#endif
AuthSocket的OnRead()方法
/// Read the packet from the client
void AuthSocket::OnRead()
{
补充知识点:
sock的I/O模式有两种,阻塞和非阻塞。在阻塞模式下,I/O操作完成前,执行操作的函数会一直等待下去,不会立即返回,这意味着应用程序很难同时通过多个建立好连接的套接字进行通行,这并不适合服务器的sock编程。在非阻塞模式下,I/O操作无论如何都会返回并交出程序的控制权。我们可以ioctlsocket函数来设置套接字的I/O模式,如:
SOCKET s = socket(AF_INET,SOCK_STREAM,0);
int nStatus = ioctlsocket(s,FIOBIO,&cmd);
当套接字被设为非阻塞后, I/O调用会立即返回,但大多情况下会调用“失败”,返回一个WOULDBLOCK错误表示请求操作在调用期间没有时间完成。所以需要通过不断检查函数返回代码以判断一个套接字何时可供读写。为了免去这样的麻烦,sock api提供了Select模型对I/O进行管理。(其他还有AsyncSelect,EventSelect,Overlapped, Completion port等)
Select模型是最为常见的I/O模型。通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否存在数据,或则能否向一个套接字写入数据。select函数的原型:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout);
其中,ndfs可以忽略,只是起到兼容的作用,readfds、writefds、exceptfds三个fd_set数据类型的参数分别指向等待可读检查的套接字组、等待可写检查的套件字组、等待错误检查的套接字组的指针。timeout指定select()最多等待的时间。
当select返回,如果调用成功,则可以判断套接字是否仍为readfds,writefds集合的一员,若是则表示套接字可读或可写
if(FD_ISSET(s,&fdread){
}
当服务器Mangosd验证完客户端身份,之后从服务器向客户端发送第一个消息(SMSG_AUTH_RESPONSE)开始,便一直用RC4对消息包头进行加密。客户端接收到消息后必须先进行解密,才能获得有效数据以便继续往下处理。
服务端是对包头信息加密,包头的定义如下:
struct ServerPktHeader
{
};
包头可能包含4个或5个字节,分别对应小数据和大数据。
开始2或3字指数据包长度size,总是以大端存放不管具体操作系统如何存放数据的。
接着两位是消息指令代码cmd,对应某个类型的消息比如SMSG_AUTH_RESPONSE。
当服务器放送消息时先将包头加了密
int WorldSocket::SendPacket (const WorldPacket& pct)
{
m_Crypt是专门用来处理对称加密解密的成员,它在处理CMSG_AUTH_SESSION时被初始化
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)『
来看一下这个初始化函数
void AuthCrypt::Init(BigNumber *K)
{
}
初始化函数先用ServerEncryptionKey,K做HmacHash运算,获得的摘要结果作为rc4加密的seed
_serverEncrypt是SARC实例,起初化操作中做了下面操作
初始化完后,当服务器要加密包头时调用
这个操作后data发生了改变,加了密,服务器将消息(加密包头+内容)发送到客户端
客户端解密
由于消息加了密,客户端不能直接使用接收到的消息,我们知道服务器是用rc4并用sessionkey和ServerEncryptionKey的HmacHash摘要结果作为rc4算法的seed.
rc4是对称加密算法,在客户端我们只需将服务器对包头做的操作重复一遍及是解密操作了。
在客户端我们有相应sessionkey,而ServerEncryptionKey是hardcode的。
补充知识:
HAMC是密钥相关的哈希运算消息认证码(keyed-Hash Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出.
RC4加密算法是Ron Rivest在1987年设计的密钥长度可变的流加密算法簇;RC4得到密文,解密过程完全相同.
标签: mangos验证消息sha1加密 | 分类: Mangos代码阅读 |
当在wow输入帐号密码登录后,客户端程序先连接的是登录服务器,(这里是叫realmd.exe的进程)通过sha1密文验证流程后,获取游戏服务器的ip,port,(这里便是mangosd.exe进程对应的ip,port)客户端接着连接这个游戏服务器。
这里阐述的是连接到mangos服务器后的验证过程.
1。 当与mangos服务器建立tcp连接后,服务器会想客户端发送消息SMSG_AUTH_CHALLENGE
int WorldSocket::open (void *a)
{
这里实际要用的信息为SMSG_AUTH_CHALLENGE这个OpCode,m_Seed一个随机数。
2.客户端获取这个OpCode为SMSG_AUTH_CHALLENGE的消息后,知道服务器要求客户端提供身份验证信息。
于是在客户端构造消息CMSG_AUTH_SESSION:
这里关键要构造sha验证密文:
20位密文 = sha(account, serverSeed,clientSeed,K)
account是帐号文本,serverSeed是SMSG_AUTH_CHALLENGE中传过来的服务端生成的随机数,clientSeed是相应的客户端生成的,K是之前验证登录服务器时生成的(客户端与服务端都有一份相同的K,服务端存放在realmd.account.sessionkey字段下)
客户端将account,clientSeed,sha密文传向服务端
3.在服务端处理客户端发送的消息
1)首先服务端读取包头大小的数据,验证包头信息
包头的结构:
struct ClientPktHeader
{
};
读取包长的数据
int WorldSocket::handle_input_missing_data (void){
。。。。。
。。。。。
m_Header是WorldSocket类的成员,定义:
并在构造函数中初始化
WorldSocket::WorldSocket (void) :
WorldHandler (),
m_Session (0),
m_RecvWPct (0),
m_RecvPct (),
m_Header (sizeof (ClientPktHeader)),
。。。。
处理包头信息
int WorldSocket::handle_input_header (void)
{
这里调用EndianConvertReverse(header.size)是因为用16位表示的header.size是按正常相反的Endian存放的,
(如果系统是小端存放,则这个size是以大端存放)
m_RecvWPct用来存放客户端发送的数据包,m_RecvPct其实只向m_RecvWPct的同一块内存区域
2)处理数据包
首先读取包头中size长的内容
int WorldSocket::handle_input_missing_data (void)『
。。。。。。。。。
。。。。。。。。。。
处理数据包
int WorldSocket::handle_input_payload (void)『
。。。。。。。。
。。。。。。。
int WorldSocket::ProcessIncoming (WorldPacket* new_pct)
{
。。。。。。。。。。。。
。。。。。。。。。
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)『
。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。
在服务端以客户端同样方式计算sha密文, 并与客户端传来的比较,如果相同则验证成功,
接着创建WorldSession实例,并初始化m_Crypt对称加密成员,之后服务器发送的消息就是加密过的了。
添加到m_Session游戏世界对象中,这一添加动作将在游戏世界主线程中被得到相应处理。
3)处理WorldSession的添加,在游戏世界对象World中
void World::UpdateSessions( uint32 diff )
{
void
World::AddSession_ (WorldSession* s)
{
。。。。。。。。。。
一些关于PlayerCache的操作
如果Session能作为ActiveSession而不是QueuedSession(等待进入游戏)则
发送SMSG_AUTH_RESPONSE消息,其内容将被加密
4. 消息样例
connect to 192.168.111.128 8085
<=192.168.111.128 8085(ACK PUSH) SMSG_AUTH_CHALLENGE
00 1A | EC 01 01 00 | 00 00 57 B9 7B 6C A3 9D | 53 F3 B9 47 | 85 6E F8 A2 | 6A 9A F4 70
F1 A4
=>192.168.111.128 8085(ACK PUSH) CMSG_AUTH_SESSION
01 16 | ED 01 00 00 | C4 30 00 00 00 00 00 00 | 41 44 4D 49 | 4E 49 53 54 | 52 41 54 4F
52 00 00 00 | 00 00 9B DD | 02 04 01 00 | 00 00 00 00
00 00 E0 AB | FE BA B0 81 | 86 C3 32 8E | B5 F9 65 42
20 2B 7C 12 | DE 76 9E 02 | 00 00 78 9C | 75 D2 31 6E
C3 30 0C 05 | 50 F5 14 5D | 72 99 3A 01 | 0C 23 D1 52
2B 73 41 4B | BF 36 61 89 | 32 64 39 4D | 72 9D 5E B4
E8 D6 02 F4 | FC 88 4F E2 | 83 AF C6 98 | 26 F2 F3 49
25 7C BC F9 | 89 71 43 82 | D4 6B 67 5E | D2 D7 E1 62
FE 79 81 90 | 2E 9B AF 9C | 45 B5 86 CA | 80 B2 4E 79
D9 E1 5A 23 | 3E 19 31 58 | 16 4E B4 68 | 43 2C 81 65
54 03 8E 14 | 21 81 8A 46 | 39 0D 54 2F | 79 DC 35 87
7B 55 F0 84 | 61 1B 5D CE | 71 55 B0 8D | 8F 65 52 4F
69 ED 71 22 | BD BB D6 F6 | 5B B9 E1 A1 | E3 C6 31 34
24 B3 AA 9D | AC 0B BC 1E | DB 55 A4 3E | FB 19 75 AF
1E 4B BE 64 | 55 DE 89 83 | 0A EE B7 51 | 7D 9F E3 04
4B 42 23 B4 | BE 5D 9E A1 | 3F 81 2B 14 | D0 CF 1C E3
1E B3 A0 FC | B5 F3 F7 E9 | FC 03 E1 91 | C8 AB
<=192.168.111.128 8085(ACK PUSH) (encrypted)
85 57 | 9E 3C 0C 00 | 00 00 00 00 00 00 00 00 | 00 24 FC 8A | 06 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 00 00 00 | 00 E1 6C F5 | 24 00 00 00 | 00 5C 62 2D
24 17 C6 17 | 4D 01 15 00 | 00 00 80 C5 | 17 4D 00 00
00 00 81 C5 | 17 4D 38 D5 | 2C B0 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00
标签: mangos游戏世界主线程 | 分类: Mangos代码阅读 |
先列一下主要相关文件名称及位置:
游戏世界定义文件
..\mangos\src\game\World.h 及 World.cpp
在类World中通过加载*.dbc,*.map,配置文件来构造游戏世界,其中包括游戏数据,设置项等。
线程基类文件
..\mangos\src\shared\Threading.h 及 Threading.cpp
在该文件中定义的类Runnable,Thread, ThreadPriority
Runnable是个接口,通过实现方法run()来定义线程实际要做的事情
Thread, ThreadPriority说白了就是对ACE线程API的Wrapper.
运行游戏世界的线程文件
..\mangos\src\mangosd\WorldRunnable.h 及 WorldRunnable.cpp
WorldRunnable顾名思义实现了Runnable接口,定义游戏世界线程循环要做的事情
其中关键是调用了World实例sWorld的Update()方法,大约在50~60行的位置:
最后看一下开启这个主线程的地方:
文件位置..\mangos\src\mangosd\Master.cpp,大约226行
上述只是摘要性地说明,一旦深入研究其中某个细节后,我会将内容相应添加进来。