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(∩_∩)…