| 簡述: 最近對高性能的服務器比較感興趣,讀過了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廣播新玩家上線(坐標)的消息。向該新玩家發送玩家信息(等級,裝備,魔法,攻擊力等)。 |