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

    关注过程,不知不觉就发现了结果原来如此...
  • 相关阅读:
    sql命令附加数据库
    数据解析0102
    xml file parser
    c#字定义异常处理类
    simple workflow implement
    High Performance Multithreaded Work Item / Event Scheduling Engine
    字符定位
    C#实现的不重复随机数序列生成算法
    Thread并发数控制
    通过Assembly来创建Type Instance
  • 原文地址:https://www.cnblogs.com/iicc/p/5058927.html
Copyright © 2020-2023  润新知