简述: 最近对高性能的服务器比较感兴趣,读过了DELPHI的Socker源码WebService及RemObject之后,高性能的服务器感兴趣。 你可能需要的以下知识才能更好的读懂一个商业源码: 1).SOCKET的I/O模型熟悉掌握。 2).面向对象技术的熟悉掌握。 3).Socket的API掌握。 4).多线程技术等。 5).一门熟悉的开发工具掌握,和多种语言的源码阅读能力。
我下的源码 LegendOfMir2_Server:共包含AdminCmd, DBSrv, GameGate, GameSvr,LoginGate, LoginSvr, SelGate七个工程文件。传奇的客户端源代码有两个工程,WindHorn和Mir2Ex。 我分析的, 主要是VC SQL版本的, DELPHI翎风源码不做分析, 另外下载了乐都WIL编辑器和乐都MPA地图编辑器这些工具.
传奇源码分析-客户端(WindHorn简述和传奇文件格式分析)
DirectX类库分析(WindHorn):
1. RegHandler.cpp 注册表访问(读写)。 2. CWHApp派生CWHWindow,CWHWindow完成窗口的注册和创建。CWHWindow派生出CWHDXGraphicWindow,CWHDXGraphicWindow调用CWHWindow完成创建窗口功能,然后再调用CreateDXG()来初始化DirectX。 3. WHDefProcess.cpp在构造函数中获得CWHDXGraphicWindow句柄。 Clear函数中调用在后台缓存上进行绘图操作,换页至屏幕。 ShowStatus函数,显示状态信息。 DefMainWndProc函数,调用CWHDXGraphicWindow->MainWndProcDXG消息处理。 4. WHImage.cpp图象处理。加载位图,位图转换。优化处理。 5. WHSurface.cpp 主页面处理。 6. WHWilTexture.cpp 材质渲染。 WILTextureContainer: WIL容器类。m_pNext指向下一个WILTextureContainer,单链表。 7. WHWilImage.cpp 从Data目录中加载Wix文件(内存映射)。 8. WHDXGraphic.cpp 处理DirectX效果。
文件类型格式探讨: Wix文件:索引文件,根据索引查找到相应数据地址(数据文件)。 // WIX 文件头格式 typedef struct tagWIXFILEIMAGEINFO { CHAR szTmp[40]; // 库文件标题 \'WEMADE Entertainment inc.\' WIL文件头 INT nIndexCount; // 图片数量 INT* pnPosition; // 位置 }WIXIMAGEINFO, *LPWIXIMAGEINFO;
我们下载一个Hedit编辑器打开一个Wil文件,分析一下。我们发现Wix文件中,0x23地址(含该地址)以前的内容是都相同的,即为:#INDX v1.0-WEMADE Entertainment inc. Ofs44 0x2C的地方:存放着0B 00 00 00,高低位转换后为:0xB转换十进制数为11(图片数量)Ofs48 0x30的地方:存放着38 04 00 00,高低位转换后为:0x438 = 1080, 这个就是图象数据的开始位置。
我们用Wil编辑打开对应的Wil文件,发现,果然有11张图片。另外我们发现,在Ofs = 44 -47之间的数据总是38 04 00 00,终于明白,所有的图片起始位置是相同的。
Wil文件: 数据文件。 前面我们说了图象数据的开始位置为0x438 = 1080, 1080中有文件开头的44字节都是相同的。所以,就是说有另外的1036字节是另有用途。1036中有1024是一个256色的调色板。 我们看到图片位置数据为: 20 03 58 02, 转化为十六进制: 0x320, 0x258 刚好就是800*600大小的图片。07 00 D4 FF。图片起始位置为: Ofs 1088: 0x440 图片大小为480000 起始位置:0x440 1088 终止位置:0x7573F 481087 为了验证数据是否正确,我们通过Wil工具,把第一幅图片导出来,然后用Hedit编辑器打开,经过对比,我们发现,数据一致。大小一致。 第二张BMP图片(图片起始位置:0x436 10078) : F0 01 69 01 , 07 00 D4 FF 刚好大小。第二张Wil起始位置:Ofs:481096 0x75748 知道了图片格式,我们可以写一个抓图片格式的程序了。
传奇源码分析-客户端(全局变量与总体执行流程) 客户端: 传奇的客户端源代码有两个工程,WindHorn和Mir2Ex。 先剖析一下WindHorn工程。 1.CWHApp、CWHWindow和CWHDXGraphicWindow。Window程序窗口的创建。 CWHApp派生CWHWindow,CWHWindow又派生CWHDXGraphicWindow。CWHWindow类 中完成窗口的注册和创建。CWHDXGraphicWindow调用CWHWindow完成创建窗口功能,然后再调用CreateDXG()来初始化DirectX。
2.CWHDefProcess派生出CloginProcess、CcharacterProcess、CgameProcess三个类。 这三个类是客户端处理的核心类。
3. 全局变量: CWHDXGraphicWindow g_xMainWnd; 主窗口类。 CLoginProcess g_xLoginProc; 登录处理。 CCharacterProcess g_xChrSelProc; 角色选择处理。 CgameProcess g_xGameProc; 游戏逻辑处理。
4.代码分析: 1.首先从LoginGate.cpp WinMain分析: g_xMainWnd定义为CWHDXGraphicWindow调用CWHWindow完成创建窗口功能,然后 调用DirectDrawEnumerateEx枚举显示设备,(执行回调函数DXGDriverEnumCallbackEx) 再调用CreateDXG()来初始化DirectX(创建DirectDraw对象, 取得独占和全屏模式, 设置显示模式等)。 g_xSound.InitMirSound创建CSound对象。 g_xSpriteInfo.SetInfo(); 初始化声音,加载Socket库之后,进行CWHDefProcess*指针赋值(事件绑定)。g_bProcState变量反应了当前游戏的状态(登录,角色选择,游戏逻辑处理)。调用Load初始化一些操作(登录,角色选择,游戏逻辑处理)。进行消息循环。 case _LOGIN_PROC: g_xLoginProc.RenderScene(dwDelay); case _CHAR_SEL_PROC: g_xChrSelProc.RenderScene(dwDelay); case _GAME_PROC: g_xGameProc.RenderScene(dwDelay); 根据g_bProcState变量标志,选择显示相应的画面。
2.接收处理网络消息和接收处理窗口消息。 在不同的状态下(登录,角色选择,游戏逻辑处理),接收到的消息(网络,窗口消息)会分派到不同的函数中处理的。这里是用虚函数处理(调用子类方法,由实际的父类完成相应的处理)。 OnMessageReceive主要处理网络消息。DefMainWndProc则处理窗体消息(按键,重绘等),创建窗体类为CWHDXGraphicWindow,回调函数为: MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) if ( m_pxDefProcess ) m_pxDefProcess->DefMainWndProc(hWnd, uMsg, wParam, lParam); else return MainWndProcDXG(hWnd, uMsg, wParam, lParam);
m_pxDefProcess->DefMainWndProc调用父类的实际处理。 在WM_PAINT事件里: g_xClientSocket
.ConnectToServer连接登陆服务器。
传奇源码分析-客户端(传奇2文件格式分析) 传奇文件类型格式探讨(一): Wix文件:索引文件,根据索引查找到相应数据地址(数据文件)。 // WIX 文件头格式 typedef struct tagWIXFILEIMAGEINFO { CHAR szTmp[40]; // 库文件标题 \'WEMADE Entertainment inc.\' WIL文件头 INT nIndexCount; // 图片数量 INT* pnPosition; // 位置 }WIXIMAGEINFO, *LPWIXIMAGEINFO;
我们下载一个Hedit编辑器打开一个Wil文件,分析一下。我们发现Wix文件中,0x23地址(含该地址)以前的内容是都相同的,即为:#INDX v1.0-WEMADE Entertainment inc. Ofs44 0x2C的地方:存放着0B 00 00 00,高低位转换后为:0xB转换十进制数为11(图片数量)Ofs48 0x30的地方:存放着38 04 00 00,高低位转换后为:0x438 = 1080, 这个就是图象数据的开始位置。
我们用Wil编辑打开对应的Wil文件,发现,果然有11张图片。另外我们发现,在Ofs = 44 -47之间的数据总是38 04 00 00,终于明白,所有的图片起始位置是相同的。
Wil文件: 数据文件。 前面我们说了图象数据的开始位置为0x438 = 1080, 1080中有文件开头的44字节都是相同的。所以,就是说有另外的1036字节是另有用途。1036中有1024是一个256色的调色板。而Wil里面的图片格式都是256色的位图储存。 我们看到图片位置数据为: 20 03 58 02, 转化为十六进制: 0x320, 0x258 刚好就是800*600大小的图片。07 00 D4 FF为固定值(标识)。图片起始位置为: Ofs 1088: 0x440 图片大小为480000 起始位置:0x440 1088 终止位置:0x7573F 481087 为了验证数据是否正确,我们通过Wil工具,把第一幅图片导出来,然后用Hedit编辑器打开,经过对比,我们发现,数据一致。大小一致。 大家看到图片1的结束位置为0fs 481077,减去1080+1 = 480000刚好800*600大小。 我们用Wil抓图工具打开看一下(确定是800*600大小):
我们导出第二张BMP图片 图片的大小为:496* 361, 我们从Wix中读出第二张图片的索引位置: 根据贴图,我们发现第二张图片的索引位置为: 40 57 07 00,转换为十六进制:0x75740,即为:481088,前面我们讲到第一张图片的结束位置是: 0fs 481077,从Wix中读出来的也刚好为第二张图片的起始位置: (我们分析Wil中的第二张图片,起始位置:0x75740 481088) : F0 01 69 01为图片长宽: 0x1F0, 0x169 为496* 361 。 07 00 D4 FF为固定值(标识)。 我们用工具打开第二张BMP图片,从起始位置,一直选取中至结束,发现刚好选496* 361字节大小。两边数据对比之后发现一致。知道了图片格式,我们可以写一个抓图片格式的程序了。
传奇源码分析-客户端(传奇2和3 文件格式分析比较) 贴这个贴子,希望大家少走弯路。网上下载的那个版本应该是从传奇2改的,传奇3的格式。分析一下源码吧,g_xLoginProc.Load(); 之后就加载m_Image.NewLoad(IMAGE_INTERFACE_1, TRUE, TRUE); 继续读Wix文件, ReadFile(hWixFile, &m_stNewWixImgaeInfo, sizeof(NEWWIXIMAGEINFO)-sizeof(INT*), &dwReadLen, NULL); // WIX 文件头格式 (56Byte)(NEW) typedef struct tagNEWWIXFILEIMAGEINFO { CHAR szTitle[20]; // 库文件标题 \'WEMADE Entertainment inc.\' WIL文件头 INT nIndexCount; // 图片数量 INT* pnPosition; // 位置 }NEWWIXIMAGEINFO, *LPNEWWIXIMAGEINFO; 不看不知道,一看吓一跳,大家看到了吧,这个是新的WIX的定义,不是传奇2的,前面分析过传奇2的图片: 0x23地址(含该地址)以前的内容是都相同的,即为:#INDX v1.0-WEMADE Entertainment inc. Ofs44 0x2C的地方:存放着0B 00 00 00,高低位转换后为:0xB转换十进制数为11(图片数量)Ofs48 0x30的地方:存放着38 04 00 00,高低位转换后为:0x438 = 1080, 这个就是图象数据的开始位置。这里才20个标题长度。 一看就不对。所以如果你下了网上的传奇3的格式,试着读传奇2的图片,是不正确的。具体大家可以调试一下,我调试过了,里面的图片数量根本不对。 汗,居然让人郁闷的是, // WIX 文件头格式 (56Byte) typedef struct tagWIXFILEIMAGEINFO { CHAR szTmp[40]; // 库文件标题 \'WEMADE Entertainment inc.\' WIL文件头 INT nIndexCount; // 图片数量 INT* pnPosition; // 位置 }WIXIMAGEINFO, *LPWIXIMAGEINFO;我用了这种格式也不对。为什么不对,因为我前面分析过了,0xB转换十进制数为11(图片数量)Ofs48 0x30的地方, 看到没有,图片数量的存放地方。 所以赶快改一下数据结构吧,不知道为什么,难道是我版本有问题,我下了几个资源文件,结果发现问题依然存在。看来不是图片的问题。 另外,下面的工程里的图片,如果要运行,不用改数据结构,请到传奇3客户端官方网站下载。我下载的是1.5版的资源文件。 是传奇2的资源文件。祝大家好运吧!
传奇文件类型格式探讨(二): // WIX 文件头格式 (NEW) typedef struct tagNEWWIXFILEIMAGEINFO { CHAR szTitle[20]; // 库文件标题 \'WEMADE Entertainment inc.\' WIL文件头 INT nIndexCount; // 图片数量 INT* pnPosition; // 位置 }NEWWIXIMAGEINFO, *LPNEWWIXIMAGEINFO;
我们下载一个Hedit编辑器打开一个Wil文件,分析一下。我们发现Wix文件中,0x13地址(含该地址)以前的内容是都相同的,即为: ‘ ’20个空格。 图片数量: nIndexCount 18
Ofs 20, 0x14的位置,存放的数据为12 00 00 00,高低位转换后为:0x12十制数为18(图片数量)。Ofs28 0x1C的地方:存放着20 00 00 00,高低位转换后为:0x20 = 32, 这个就是图象数据的开始位置。 我们用Wil编辑打开对应的Wil文件,发现,果然有17张图片(减1)。另外我们发现,在Ofs28 0x1C的地方= 28 -31之间的数据总是20 00 00 00,终于明白,所有的图片起始位置是相同的。
抓图分析,自己就再分析一下吧,和传奇2的结构差不多。
传奇源码分析-客户端(游戏逻辑处理源分析一) 登录处理事件: 0.WinMain主函数调用g_xLoginProc.Load();加载图片等初始化,设置g_bProcState 的状态。 1.CLoginProcess::OnKeyDown-> m_xLogin.OnKeyDown->g_xClientSocket.OnLogin; WSAAsyncSelect模型ID_SOCKCLIENT_EVENT_MSG,因此,(登录,
角色选择,游戏逻辑处理)都回调g_xClientSocket.OnSocketMessage(wParam, lParam)进行处理。 OnSocketMessage函数中:FD_READ事件中: 2.g_bProcState判断当前状态,_GAME_PROC时,把GameGate的发送过来的消息压入PacketQ队列中,再进行处理。否则则调用OnMessageReceive(虚方法,根据g_bProcState状态,调用CloginProcess或者是CcharacterProcess的OnMessageReceive方法)。 3.CloginProcess:调用OnSocketMessageRecieve处理返回情况。如果服务器验证失败(SM_ID_NOTFOUND, SM_PASSWD_FAIL)消息,否则收到SM_PASSOK_SELECTSERVER消息(SelGate服务器列表消息)。m_Progress = PRG_SERVER_SELE;进行下一步选择SelGate服务器操作。 4. m_xSelectSrv.OnButtonDown->CselectSrv. OnButtonUp-> g_xClientSocket.OnSelectServer(CM_SELECTSERVER),得到真正的IP地址。调用OnSocketMessageRecieve处理返回的SM_SELECTSERVER_OK消息。并且断开与loginSrv服务器连接。 g_xClientSocket.DisconnectToServer();设置状态为PRG_TO_SELECT_CHR状态。
角色选择处理: 1. WinMain消息循环处理:g_xLoginProc.RenderScene(dwDelay)-> RenderScroll-> SetNextProc调用 g_xClientSocket.m_pxDefProc = g_xMainWnd.m_pxDefProcess = &g_xChrSelProc; g_xChrSelProc.Load(); g_bProcState = _CHAR_SEL_PROC;
2.g_xChrSelProc.Load();连接SelGate服务器(从LoginGate服务器得到IP地址)。 g_xClientSocket.OnQueryChar();查询用户角色信息,发送消息:CM_QUERYCHR,设置状态为_CHAR_SEL_PROC, m_Progress = PRG_CHAR_SELE; 在OnSocketMessageRecieve函数中接收到SelGate服务器发送的消息。
3.点击ChrStart按钮:g_xChrSelProc.OnLButtonDown-> CSelectChr::OnButtonUp-> g_xClientSocket.OnSelChar->发送CM_SELCHR消息到SelGate服务器。
4.CClientSocket::OnSocketMessage->CCharacterProcess::OnMessageReceive (SM_STARTPLAY) 接受到SelGate服务器发送的GameGate服务器IP地址,并断开与SelGate服务器的连接。m_xSelectChr.m_nRenderState = 2; 5. WinMain消息循环处理:g_xLoginProc.RenderScene -> m_xSelectChr.Render(nLoopTime);-> CSelectChr::Render(INT nLoopTime)-> m_nRenderState = m_nRenderState + 10; 为12-> CCharacterProcess::RenderScene执行
m_Progress = PRG_SEL_TO_GAME; m_Progress = PRG_PLAY_GAME; SetNextProc();
6.SetNextProc();执行: g_xGameProc.Load(); g_bProcState = _GAME_PROC;进行游戏状态。
游戏逻辑处理: 1.客户端处理: CGameProcess::Load() 初始化游戏环境,加载地图等操作,调用ConnectToServer(m_pxDefProc->OnConnectToServer)连接到GameGate游戏网关服务器(DBSrv处理后经SelGate服务器返回的GameGate服务器IP地址)。 CClientSocket->ConnectToServer调用connect时,由GameGate服务器发送GM_OPEN消息到GameSrv服务器。WSAAsyncSelect I/O模型回调函数 g_xClientSocket.OnSocketMessage。然后由m_pxDefProc->OnConnectToServer()调用CGameProcess::OnConnectToServer()函数,调用:g_xClientSocket.SendRunLogin。
2. GameGate服务器ServerWorkerThread处理: GameGate服务器ServerWorkerThread收到消息,ThreadFuncForMsg处理数据,生成MsgHdr结构,并设置 MsgHdr.nCode = 0xAA55AA55; //数据标志 MsgHdr.wIdent = GM_DATA; //数据类型
3. GameSrv服务器ServerWorkerThread线程处理 GameSrv服务器ServerWorkerThread线程处理调用DoClientCertification设置用户信息,及USERMODE_LOGIN的状态。并且调用LoadPlayer(CUserInfo* pUserInfo)函数-> LoadHumanFromDB-> SendRDBSocket发送DB_LOADHUMANRCD请求,返回该玩家的所有数据信息。
4. 客户端登录验证(GameSrv服务器的线程ProcessLogin处理) 用户的验证是由GameSrv服务器的线程ProcessLogin处理。g_xReadyUserInfoList2列表中搜索,判断用户是否已经登录,一旦登录就调用LoadPlayer(这里两个参数): a. 设置玩家游戏状态。m_btCurrentMode状态为USERMODE_PLAYGAME b. 加载物品,个人设置,魔法等。 c. pUserInfo->m_pxPlayerObject->Initialize();初始化用户信息,加载用户坐标,方向,地图。 Initialize执行流程: 1) AddProcess(this, RM_LOGON, 0, 0, 0, 0, NULL);加入登录消息。 2) m_pMap->AddNewObject 地图中单元格(玩家列表)加入该游戏玩家。OS_MOVINGOBJECT玩家状态。 3) AddRefMsg(RM_TURN 向周围玩家群发 RM_TURN消息。以玩家自己为中心,以24*24的区域里,向这个区域所属的块里的所有玩家列表发送消息)广播 AddProcess。 4) RecalcAbilitys 设置玩家的能力属性(攻击力(手,衣服),武器力量等)。 5) 循环处理本游戏玩家的附属物品,把这些物品的力量加到(手,衣服等)的攻击力量里。 6) RM_CHARSTATUSCHANGED消息,通知玩家状态改变消息。 7) AddProcess(this, RM_ABILITY, 0, 0, 0, 0, NULL); 等级 AddProcess(this, RM_SUBABILITY, 0, 0, 0, 0, NULL); AddProcess(this, RM_DAYCHANGING, 0, 0, 0, 0, NULL); 校时 AddProcess(this, RM_SENDUSEITEMS, 0, 0, 0, 0, NULL); 装备 AddProcess(this, RM_SENDMYMAGIC, 0, 0, 0, 0, NULL); 魔法 SysMsg(szMsg, 1) 攻击力 并把用户数据从g_xReadyUserInfoList2列表中删除。
说明: 一旦通过验证,就从验证列表中该玩家,改变玩家状态,LoadPlayer加载用户资源(地图中加入用户信息,向用户24*24区域内的块内玩家发送上线消息GameSrv广播新玩家上线(坐标)的消息。向该新玩家发送玩家信息(等级,装备,魔法,攻击力等)。
|