1-4是大概把GAMECENTER過了一遍,終於把消息機制入了一點門,接下來是服務端第一個服務的學習--DBServer.是一個數據庫服務器,在學習這個單元的時候,發現了這個端的大概由來,不知道是哪個大牛反編譯后重寫的,看來之前我理解的是錯誤的,代碼雜亂的原因不是沒有考慮到正題設計,這是由DEDEDARK反編譯的端,根據自己的經驗補寫的實現代碼,不知道我這輩子能不能達到這樣的水平,那得需要對匯編多熟悉才行啊.
function TFrmNewChr.sub_49BD60(var sChrName: string): Boolean;//反編譯的函數 //0x0049BD60 begin Result := False; EdName.Text := ''; Self.ShowModal; sChrName := Trim(EdName.Text); if sChrName <> '' then Result := True; end; //這個函數是增加新角色,能看到匯編代碼痕跡,單元里邊還有DEDEDARK的注釋說明
4 DBServer
4.1 DBSMain.pas
先說主單元,這是整個傳奇服務端的數據庫服務器,這個單元結構還是比較清晰的,代碼1500行左右,接口部分聲明的新對象也不多,主要是VCL聲明和過程,但是這個服務調用的其他模塊較多,主要用於數據庫(人物,物品,技能等)的處理.
unit DBSMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, JSocket, Buttons, IniFiles, Menus, Grobal2, HumDB, DBShare, ComCtrls, ActnList, AppEvnts, DB, DBTables, Common; type {定義服務器信息} TServerInfo = record nSckHandle: Integer;//socket句柄 RecvBuff: PChar; //接收數據緩沖區 BuffLeng: Integer; //緩沖區大小 Socket: TCustomWinSocket;//這里直接繼承的TCustomWinSocket,現在應該不用這樣了 end; pTServerInfo = ^TServerInfo;//定義為指針 TFrmDBSrv = class(TForm) ServerSocket: TServerSocket; Timer1: TTimer; {.......中間的省略,都是VLC的聲明} procedure X1Click(Sender: TObject); procedure N3Click(Sender: TObject); procedure F1Click(Sender: TObject); procedure T2Click(Sender: TObject); procedure MENU_MANAGE_TOOLClick(Sender: TObject); private n344: Integer;//這兩個暫時還不知道 n348: Integer; ServerList: TList; //服務器列表信息 m_boRemoteClose: Boolean; //連接標志 procedure ProcessServerPacket(ServerInfo: pTServerInfo);//數據包處理過程 {發送數據} procedure SendSocket(Socket: TCustomWinSocket; SendBuff: PChar; BuffLen: Integer); {這是讀取角色數據的過程,帶有非法連接處理} procedure LoadHumanRcd(RecvBuff: PChar; BuffLen, QueryID: Integer; Socket: TCustomWinSocket); {角色退出時保存角色數據} procedure SaveHumanRcd(nRecog, QueryID: Integer; RecvBuff: PChar; BuffLen: Integer; Socket: TCustomWinSocket); {清理} procedure ClearSocket(Socket: TCustomWinSocket); {獲取端口列表} procedure ShowModule(); {加載物品數據庫} function LoadItemsDB(): Integer; {加載技能數據庫} function LoadMagicDB(): Integer; public {復制人物數據} function CopyHumData(sSrcChrName, sDestChrName, sUserId: string): Boolean; {刪除人物數據} procedure DelHum(sChrName: string); {服務器消息處理函數,用於和其他進程通信} procedure MyMessage(var MsgData: TWmCopyData); message WM_COPYDATA; end; var FrmDBSrv: TFrmDBSrv;
大部分的處理過程是針對SOCKET的編程和數據庫的讀寫,開始我還覺得為什么不用大型數據庫,看完之后,大概了解到傳奇服務端數據本來就不多,這個服務端是為多機架設而寫的,一般數據庫,網關服務都在單獨的服務器上,所以單個服務端最大在線一般不超過2000人,所以用小型的DBC2000還是足夠的,大型數據庫除非是專門的數據庫服務器,那樣會提高並發操作的效率,可是對於這套架構來說,無異於所有代碼都需要重構了,先學習基本的數據處理方式,學到經驗還可以用到多層數據庫開發中,這也算一個捷徑吧.
4.2 DBShare.pas
對於數據庫服務器的共享數據單元倒沒有什么特別的,主單元的數據處理函數一部分放在了這里,其他的大部分都是變量.
unit DBShare; interface uses Windows, Messages, Classes, SysUtils, StrUtils, JSocket, WinSock, IniFiles, Grobal2, MudUtil, Common; const g_sUpDateTime = '修改日期: 2015/12/09'; SIZEOFTHUMAN = 44145; //44032 type TGList = class(TList) private GLock: TRTLCriticalSection; public constructor Create; destructor Destroy; override; procedure Lock; procedure UnLock; end; TSockaddr = record //用於攻擊檢測用的 nIPaddr: Integer; dwStartAttackTick: LongWord; nAttackCount: Integer; end; pTSockaddr = ^TSockaddr; TCheckCode = record //測試用的,正式端不包含 dwThread0: LongWord; end; TGateInfo = record //網關信息 Socket: TCustomWinSocket; sGateaddr: string; //0x04 sText: string; //0x08 sSendMsg: string; UserList: TList; //0x0C dwTick10: LongWord; //0x10 nGateID: Integer; //網關ID end; pTGateInfo = ^TGateInfo; TUserInfo = record //角色信息 sAccount: string; //0x00 sUserIPaddr: string; //0x0B sGateIPaddr: string; sConnID: string; //0x20 sSockIndex: string; nSessionID: Integer; //0x24 Socket: TCustomWinSocket; boChrSelected: Boolean; //0x30 boChrQueryed: Boolean; //0x31 dwTick34: LongWord; //0x34 dwChrTick: LongWord; //0x38 nSelGateID: ShortInt; //角色網關ID nDataCount: Integer; boWaitMsg: Boolean; nWaitID: Integer; sCreateChrMsg: string; end; pTUserInfo = ^TUserInfo; TRouteInfo = record //路由配置信息 nGateCount: Integer; sSelGateIP: string[15]; sGameGateIP: array[0..7] of string[15]; nGameGatePort: array[0..7] of Integer; end; pTRouteInfo = ^TRouteInfo; procedure LoadConfig(); //加載服務端設置 procedure LoadIPTable(); //從設置文件里邊加載IP列表 function GetCodeMsgSize(X: Double): Integer; //取得消息編號 function InClearMakeIndexList(nIndex: Integer): Boolean; // procedure WriteLogMsg(sMsg: string); //寫入日志信息 function CheckServerIP(sIP: string): Boolean; //監測連接IP的合法性 procedure SendGameCenterMsg(wIdent: Word; sSendMsg: string); //向引擎控制台發送消息 procedure MainOutMessage(sMsg: string); //發送消息到主界面 function GetMagicName(wMagicId: Word): string; //獲取技能名稱 function GetStdItemName(nPosition: Integer): string;//取得物品名稱 function CheckFiltrateUserName(sName: string): Boolean;//檢查過濾角色 procedure LoadFiltrateName(); //讀取過濾 function GetWaitMsgID(): Integer;//取得等待處理的消息編號 var sDataDBFilePath: string = '.\DB\'; nServerPort: Integer = 6000; sServerAddr: string = '0.0.0.0'; g_nGatePort: Integer = 5100; g_sGateAddr: string = '0.0.0.0'; nIDServerPort: Integer = 5600; sIDServerAddr: string = '127.0.0.1'; g_nWaitMsgIndex: Integer = 0; g_boTestServer: Boolean = True; {以下暫時還不知道是干什么的,先不做猜測} HumDB_CS: TRTLCriticalSection; //0x004ADACC g_FiltrateUserName: TStringList; n4ADAE4: Integer; n4ADAE8: Integer; n4ADAEC: Integer; n4ADAF0: Integer; boDataDBReady: Boolean; //0x004ADAF4 n4ADAFC: Integer; n4ADB00: Integer; n4ADB04: Integer; boHumDBReady: Boolean; //0x4ADB08 n4ADBF4: Integer; n4ADBF8: Integer; n4ADBFC: Integer; n4ADC00: Integer; n4ADC04: Integer; boAutoClearDB: Boolean; //0x004ADC08 g_nQueryChrCount: Integer; //0x004ADC0C nHackerNewChrCount: Integer; //0x004ADC10 nHackerDelChrCount: Integer; //0x004ADC14 nHackerSelChrCount: Integer; //0x004ADC18 n4ADC1C: Integer; n4ADC20: Integer; n4ADC24: Integer; n4ADC28: Integer; n4ADC2C: Integer; n4ADB10: Integer; n4ADB14: Integer; n4ADB18: Integer; n4ADBB8: Integer; bo4ADB1C: Boolean; //以下是定義服務器設置變量 sServerName: string = '新熱血傳奇'; sConfFileName: string = '.\Dbsrc.ini'; sConfClass: string = 'DBServer'; sGateConfFileName: string = '.\!serverinfo.txt'; sServerIPConfFileNmae: string = '.\!addrtable.txt'; sFiltrateUserName: string = '.\FUserName.txt'; sHeroDB: string = 'HeroDB'; sMapFile: string; DenyChrNameList: TStringList; ServerIPList: TStringList; StdItemList: TList; MagicList: TList; g_SortMinLevel: Integer = 0; g_SortMaxLevel: Integer = 200; g_boAutoSort: Boolean = True; g_boSortClass: Boolean = False; g_btSortHour: Byte = 0; g_btSortMinute: Byte = 4; g_boArraySort: Boolean = False; g_boArraySortTime: LongWord; g_nClearRecordCount: Integer; g_nClearIndex: Integer; //0x324 g_nClearCount: Integer; //0x328 g_nClearItemIndexCount: Integer; boOpenDBBusy: Boolean; //0x350 g_dwGameCenterHandle: THandle; g_boDynamicIPMode: Boolean = False; g_CheckCode: TCheckCode; g_ClearMakeIndex: TStringList; g_RouteInfo: array[0..19] of TRouteInfo; g_MainMsgList: TStringList; g_OutMessageCS: TRTLCriticalSection; ProcessHumanCriticalSection: TRTLCriticalSection; IDSocketConnected: Boolean; UserSocketClientConnected: Boolean; ServerSocketClientConnected: Boolean; DataManageSocketClientConnected: Boolean; ID_sRemoteAddress: string; User_sRemoteAddress: string; Server_sRemoteAddress: string; DataManage_sRemoteAddress: string; ID_nRemotePort: Integer; User_nRemotePort: Integer; Server_nRemotePort: Integer; DataManage_nRemotePort: Integer; dwKeepAliveTick: LongWord; dwKeepIDAliveTick: LongWord; dwKeepServerAliveTick: LongWord; const tDBServer = 0; implementation {實現部分不是很復雜,就不再注釋了,不過有的涉及到之前提到的,還有其他單元引用的} uses DBSMain, HUtil32; procedure LoadIPTable(); begin ServerIPList.Clear; try ServerIPList.LoadFromFile(sServerIPConfFileNmae); except MainOutMessage('加載IP列表文件 ' + sServerIPConfFileNmae + ' 出錯!!!'); end; end; function GetWaitMsgID(): Integer; begin Inc(g_nWaitMsgIndex); if g_nWaitMsgIndex <= 0 then g_nWaitMsgIndex := 1; Result := g_nWaitMsgIndex; end; procedure LoadConfig(); var Conf: TIniFile; begin Conf := TIniFile.Create(sConfFileName); if Conf <> nil then begin sServerName := Conf.ReadString(sConfClass, 'ServerName', sServerName); nServerPort := Conf.ReadInteger(sConfClass, 'ServerPort', nServerPort); sServerAddr := Conf.ReadString(sConfClass, 'ServerAddr', sServerAddr); g_nGatePort := Conf.ReadInteger(sConfClass, 'GatePort', g_nGatePort); g_sGateAddr := Conf.ReadString(sConfClass, 'GateAddr', g_sGateAddr); sIDServerAddr := Conf.ReadString(sConfClass, 'IDSAddr', sIDServerAddr); nIDServerPort := Conf.ReadInteger(sConfClass, 'IDSPort', nIDServerPort); sHeroDB := Conf.ReadString(sConfClass, 'DBName', sHeroDB); sDataDBFilePath := Conf.ReadString(sConfClass, 'DBDir', sDataDBFilePath); g_boTestServer := not Conf.ReadBool(sConfClass, 'NotRepeatName', not g_boTestServer); g_boAutoSort := Conf.ReadBool(sConfClass, 'AutoSort', g_boAutoSort); g_boSortClass := Conf.ReadBool(sConfClass, 'SortClass', g_boSortClass); g_btSortHour := Conf.ReadInteger(sConfClass, 'SortHour', g_btSortHour); g_btSortMinute := Conf.ReadInteger(sConfClass, 'SortMinute', g_btSortMinute); g_SortMinLevel := Conf.ReadInteger(sConfClass, 'SortMinLevel', g_SortMinLevel); g_SortMaxLevel := Conf.ReadInteger(sConfClass, 'SortMaxLevel', g_SortMaxLevel); Conf.WriteString(sConfClass, 'ServerName', sServerName); Conf.WriteInteger(sConfClass, 'ServerPort', nServerPort); Conf.WriteString(sConfClass, 'ServerAddr', sServerAddr); Conf.WriteInteger(sConfClass, 'GatePort', g_nGatePort); Conf.WriteString(sConfClass, 'GateAddr', g_sGateAddr); Conf.WriteString(sConfClass, 'IDSAddr', sIDServerAddr); Conf.WriteInteger(sConfClass, 'IDSPort', nIDServerPort); Conf.WriteString(sConfClass, 'DBName', sHeroDB); Conf.WriteString(sConfClass, 'DBDir', sDataDBFilePath); Conf.WriteBool(sConfClass, 'AutoSort', g_boAutoSort); Conf.WriteBool(sConfClass, 'SortClass', g_boSortClass); Conf.WriteInteger(sConfClass, 'SortHour', g_btSortHour); Conf.WriteInteger(sConfClass, 'SortMinute', g_btSortMinute); Conf.WriteInteger(sConfClass, 'SortMinLevel', g_SortMinLevel); Conf.WriteInteger(sConfClass, 'SortMaxLevel', g_SortMaxLevel); Conf.WriteBool(sConfClass, 'NotRepeatName', not g_boTestServer); Conf.Free; end; LoadIPTable(); end; function GetStdItemName(nPosition: Integer): string; var StdItem: pTStdItem; begin if (nPosition - 1 >= 0) and (nPosition < StdItemList.Count) then begin StdItem := StdItemList.Items[nPosition - 1]; if StdItem <> nil then begin Result := StdItem.Name; end; end; end; function GetMagicName(wMagicId: Word): string; var i: Integer; Magic: pTMagic; begin for i := 0 to MagicList.Count - 1 do begin Magic := MagicList.Items[i]; if Magic <> nil then begin if Magic.wMagicId = wMagicId then begin Result := Magic.sMagicName; break; end; end; end; end; function GetCodeMsgSize(X: Double): Integer; begin if INT(X) < X then Result := TRUNC(X) + 1 else Result := TRUNC(X) end; function InClearMakeIndexList(nIndex: Integer): Boolean; var i: Integer; begin Result := False; for i := 0 to g_ClearMakeIndex.Count - 1 do begin if nIndex = Integer(g_ClearMakeIndex.Objects[i]) then begin Result := True; break; end; end; end; procedure MainOutMessage(sMsg: string); begin EnterCriticalSection(g_OutMessageCS); try g_MainMsgList.Add(sMsg); finally LeaveCriticalSection(g_OutMessageCS); end; end; procedure WriteLogMsg(sMsg: string); begin end; function CheckServerIP(sIP: string): Boolean; var i: Integer; begin Result := False; for i := 0 to ServerIPList.Count - 1 do begin if CompareText(sIP, ServerIPList.Strings[i]) = 0 then begin Result := True; break; end; end; end; procedure SendGameCenterMsg(wIdent: Word; sSendMsg: string); var SendData: TCopyDataStruct; nParam: Integer; begin nParam := MakeLong(Word(tDBServer), wIdent); SendData.cbData := Length(sSendMsg) + 1; GetMem(SendData.lpData, SendData.cbData); StrCopy(SendData.lpData, PChar(sSendMsg)); SendMessage(g_dwGameCenterHandle, WM_COPYDATA, nParam, Cardinal(@SendData)); FreeMem(SendData.lpData); end; function CheckFiltrateUserName(sName: string): Boolean; var i: integer; begin Result := False; for I := 0 to g_FiltrateUserName.Count - 1 do begin if AnsiContainsText(sName, g_FiltrateUserName.Strings[I]) then begin Result := True; break; end; end; end; constructor TGList.Create; begin inherited Create; InitializeCriticalSection(GLock); end; destructor TGList.Destroy; begin DeleteCriticalSection(GLock); inherited; end; procedure TGList.Lock; begin EnterCriticalSection(GLock); end; procedure TGList.UnLock; begin LeaveCriticalSection(GLock); end; procedure LoadFiltrateName(); var i: Integer; TempList: TStringList; sStr: string; begin g_FiltrateUserName.Clear; TempList := TStringList.Create; TempList.Clear; try if FileExists(sFiltrateUserName) then begin TempList.LoadFromFile(sFiltrateUserName); for i := 0 to TempList.Count - 1 do begin sStr := TempList.Strings[I]; if (Length(sStr) > 0) and (sStr[1] <> ';') then g_FiltrateUserName.Add(sStr); end; end else begin TempList.Add(';創建人物過濾字符,一行一個過濾'); TempList.SaveToFile(sFiltrateUserName); end; finally TempList.Free; end; end; initialization begin InitializeCriticalSection(g_OutMessageCS); InitializeCriticalSection(HumDB_CS); g_MainMsgList := TStringList.Create; DenyChrNameList := TStringList.Create; ServerIPList := TStringList.Create; g_ClearMakeIndex := TStringList.Create; StdItemList := TList.Create; MagicList := TList.Create; g_FiltrateUserName := TStringList.Create; end; finalization begin DeleteCriticalSection(HumDB_CS); DeleteCriticalSection(g_OutMessageCS); DenyChrNameList.Free; ServerIPList.Free; g_ClearMakeIndex.Free; g_MainMsgList.Free; StdItemList.Free; MagicList.Free; g_FiltrateUserName.Free; end; end.
接下來還有一個人物數據單元HUMDB.pas,需要先把之前的復習幾遍才能去看,因為涉及到數據文件的讀寫,對於文件的學習需求馬上就到來了,這些代碼我都新建一個程序把它們一點一點敲進去編譯一遍,然后再去看源代碼的大概結構和關系,這樣學習很費時間,但是我覺得比我一下子去學習若干基礎性的東西要理解的快一點,當把整個服務端都初步過了一遍后,我會回頭將記下來的需要鞏固的基礎性東西都重新練習即便,我發現,在寫第一遍的時候是模棱兩可,第二遍就不知不覺知道了某些對象和函數到底是干什么用的,第三遍的時候我大概能想到通過自己的方式去實現一些函數和過程,甚至可以增加和去掉某些不需要的結構變量,程序的功能正常運行,也許更改的東西不合理,但是鍛煉了我的動手能力,對我的水平來說,光看一些優秀的代碼我是學不到東西的,因為不動手,我看十幾遍也不知道那到底要表達什么.
我學習的時候一般都開兩個DELPHI窗口,不知道有沒有什么更好的辦法,同時開兩個代碼提示就看不到了,不過這倒是提高了我的打字速度O(∩_∩)…