DELPHI版传奇引擎学习菜鸟篇(applem2)-05
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(∩_∩)…