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