Mir2源碼詳解之服務端-登錄網關(LoginGate)


傳奇這款游戲,一直對我的影響很大。當年為了玩傳奇,逃課,被老師叫過N次家長。言歸正傳,網上有很多源碼,當然了,都是delphi的。並且很多源碼還不全,

由於一直學習的c、c++。delphi還真不懂。無奈硬着頭皮上。好了。廢話不多說。開始。

 

登錄網關,負責游戲最開始的登錄處理(與賬戶服務器LoginSvr通訊)。驗證登錄器輸入的賬戶密碼是否正確。

界面上的控件很多。其實干活的就 就 三個:“TServerSocket”、“TClientSocket”、“DecodeTimer”這三個控件。

 

ServerSocket:負責與登錄器進行通訊,它做的操作:

1、接收連接,代碼如下:

{
函數功能:接受客戶端連接,發送消息到 登錄服務器
}
procedure TFrmMain.ServerSocketClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  UserSession:pTUserSession;
  sRemoteIPaddr,sLocalIPaddr:String;
  nSockIndex:Integer;
  IPaddr  :pTSockaddr;
begin
  Socket.nIndex:=-1;
  // 客戶端IP地址
  sRemoteIPaddr:=Socket.RemoteAddress;

  if g_boDynamicIPDisMode then  begin
    sLocalIPaddr:=ClientSocket.Socket.RemoteAddress;
  end else begin
    sLocalIPaddr:=Socket.LocalAddress;
  end;

  // 過濾ip
  if IsBlockIP(sRemoteIPaddr) then begin
    MainOutMessage('過濾連接: ' + sRemoteIPaddr,1);
    Socket.Close;
    exit;
  end;
  // 當前IP是否可以連接
  if IsConnLimited(sRemoteIPaddr) then begin
    case BlockMethod of
    // 斷開
      mDisconnect: begin
        Socket.Close;
      end;
    // 動態過濾
      mBlock: begin
        New(IPaddr);
        IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
        TempBlockIPList.Add(IPaddr);
        CloseConnect(sRemoteIPaddr);
      end;
    // 永久過濾
      mBlockList: begin
        New(IPaddr);
        IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
        BlockIPList.Add(IPaddr);
        CloseConnect(sRemoteIPaddr);
      end;
    end;
    MainOutMessage('端口攻擊: ' + sRemoteIPaddr,1);
    exit;
  end;

  // 如果網關准備好了
  if boGateReady then begin
    for nSockIndex:= 0 to GATEMAXSESSION - 1 do begin
      UserSession:=@g_SessionArray[nSockIndex];
      if UserSession.Socket = nil then begin
        UserSession.Socket:=Socket;
        UserSession.sRemoteIPaddr:=sRemoteIPaddr;
        UserSession.nSendMsgLen:=0;
        UserSession.bo0C:=False;
        UserSession.dw10Tick:=GetTickCount();
        UserSession.dwConnctCheckTick:=GetTickCount();

        UserSession.boSendAvailable:=True;
        UserSession.boSendCheck:=False;
        UserSession.nCheckSendLength:=0;
        UserSession.n20:=0;
        UserSession.dwUserTimeOutTick:=GetTickCount();
        UserSession.SocketHandle:=Socket.SocketHandle;
        UserSession.sIP:=sRemoteIPaddr;
        UserSession.MsgList.Clear;
        Socket.nIndex:=nSockIndex;
        Inc(nSessionCount);
        break;
      end;
    end;
    // 和本地登錄服務器進行通訊
    if Socket.nIndex >= 0 then begin
      ClientSocket.Socket.SendText('%O' +
                                   IntToStr(Socket.SocketHandle) +
                                   '/' +
                                   sRemoteIPaddr +
                                   '/' +
                                   sLocalIPaddr +
                                   '$');
      MainOutMessage('Connect: ' + sRemoteIPaddr,5);
    end else begin
      Socket.Close;
      MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);
    end;
  end else begin //0x004529EF
    Socket.Close;
    MainOutMessage('Kick Off: ' + sRemoteIPaddr,1);
  end;
end;

 說白了,別看 那些IP過濾規則和連接限制什么的。就是 來了一用戶,直接保存到一個 UserSession中。然后通知LoginSvr,有人連接了。。現在市面上的什么:GOM引擎、Hero、HGE(原3K、IGE)、Legend、GEEM2、77M2都是換湯不換葯。變的就是 加密方式。這里不得不說,JsocketJ就是TServerSocket、TClientSocket的控件,懶得看源碼,看了下其屬性,大致就可以了解到。

是采用的線程池的select模型。早期的游戲,都采用這種方式,不過,對於私*服,連接量不大,完全足夠應付。其實有更好的解決辦法,那就是IOCP來管理。效率更高。windows下最適合的模型了。

2、斷開連接,代碼如下:

procedure TFrmMain.ServerSocketClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  I:Integer;
  UserSession:pTUserSession;
  nSockIndex:Integer;
  sRemoteIPaddr:String;
  IPaddr  :pTSockaddr;
  nIPaddr :Integer;
begin
  sRemoteIPaddr:=Socket.RemoteAddress;
  nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
  nSockIndex:=Socket.nIndex;
  for I := 0 to CurrIPaddrList.Count - 1 do begin
    IPaddr:=CurrIPaddrList.Items[I];
    if IPaddr.nIPaddr = nIPaddr then begin
      Dec(IPaddr.nCount);
      if IPaddr.nCount <= 0 then begin
        Dispose(IPaddr);
        CurrIPaddrList.Delete(I);
      end;
      Break;  
    end;
      
  end;
  if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin
    UserSession:=@g_SessionArray[nSockIndex];
    UserSession.Socket:=nil;
    UserSession.sRemoteIPaddr:='';
    UserSession.SocketHandle:=-1;
    UserSession.MsgList.Clear;
    Dec(nSessionCount);
    if boGateReady then begin
      ClientSocket.Socket.SendText('%X' +
                                   IntToStr(Socket.SocketHandle) +
                                   '$');
      MainOutMessage('DisConnect: ' + sRemoteIPaddr,5);
    end;
  end;
end;

 刪除,釋放,並通知 LoginSvr,有人斷開連接了。。。

3、接收數據,代碼如下:

procedure TFrmMain.ServerSocketClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  UserSession:pTUserSession;
  nSockIndex:Integer;
  sReviceMsg,s10,s1C:String;
  nPos:Integer;
  nMsgLen:Integer;
begin
  nSockIndex:=Socket.nIndex;
  if (nSockIndex >= 0) and (nSockIndex < GATEMAXSESSION) then begin
    UserSession:=@g_SessionArray[nSockIndex];
    sReviceMsg:=Socket.ReceiveText;
    if (sReviceMsg <> '') and (boServerReady) then begin
      nPos:=Pos('*',sReviceMsg);
      if nPos > 0 then begin
        UserSession.boSendAvailable:=True;
        UserSession.boSendCheck:=False;
        UserSession.nCheckSendLength:=0;
        s10:=Copy(sReviceMsg,1,nPos -1);
        s1C:=Copy(sReviceMsg,nPos + 1,Length(sReviceMsg) - nPos);
        sReviceMsg:=s10 + s1C;
      end;
      nMsgLen:=length(sReviceMsg);
      if (sReviceMsg <> '') and (boGateReady) and (not boKeepAliveTimcOut)then begin
        UserSession.dwConnctCheckTick:=GetTickCount();
        if (GetTickCount - UserSession.dwUserTimeOutTick) < 1000 then begin
          Inc(UserSession.n20,nMsgLen);
        end else UserSession.n20:= nMsgLen;
        ClientSocket.Socket.SendText('%A' +
                                     IntToStr(Socket.SocketHandle) +
                                     '/' +
                                     sReviceMsg +
                                     '$');
      end;
    end;
  end;
end;

 拿到數據,轉發到 登錄賬戶服務器。。。。。它主要干的事完了。。

 

ClientSocket:負責與LoginSvr進行通訊,它做的主要工作就是,

procedure TFrmMain.ClientSocketRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  sRecvMsg:String;
begin
  sRecvMsg:=Socket.ReceiveText;
  ClientSockeMsgList.Add(sRecvMsg);
end;

 你沒有看錯,就是接收到數據。然后保存到鏈表,然后等待定時器來解析操作。。

 

DecodeTimer 定時器的工作。源碼中設置的 時間精度是 1毫秒:

procedure TFrmMain.DecodeTimerTimer(Sender : TObject);
var
  sProcessMsg   :String;
  sSocketMsg    :String;
  sSocketHandle :String;
  nSocketIndex  :Integer;
  nMsgCount     :Integer;
  nSendRetCode  :Integer;
  nSocketHandle :Integer;
  dwDecodeTick  :LongWord;
  dwDecodeTime  :LongWord;
  sRemoteIPaddr :String;
  UserSession   :pTUserSession;
  IPaddr  :pTSockaddr;
begin
  ShowMainLogMsg();
  if boDecodeLock or (not boGateReady)then exit;

  try
    dwDecodeTick:=GetTickCount();
    boDecodeLock:=True;
    sProcessMsg:='';
    while (True) do begin
      if ClientSockeMsgList.Count <= 0 then break;
       sProcessMsg:=sProcMsg + ClientSockeMsgList.Strings[0];
       sProcMsg:='';
       ClientSockeMsgList.Delete(0);
       while (True) do begin
         if TagCount(sProcessMsg,'$') < 1 then break;
         sProcessMsg:=ArrestStringEx(sProcessMsg,'%','$',sSocketMsg);
         if sSocketMsg = ''then break;
         if sSocketMsg[1] = '+' then begin
           if sSocketMsg[2] = '-' then begin
             CloseSocket(Str_ToInt(Copy(sSocketMsg,3,Length(sSocketMsg) - 2),0));
             Continue;
           end else begin //0x004521B7
             dwKeepAliveTick:=GetTickCount();
             boKeepAliveTimcOut:=False;
             Continue;
           end;
         end; //0x004521CD
         sSocketMsg:=GetValidStr3(sSocketMsg,sSocketHandle,['/']);
         nSocketHandle:=Str_ToInt(sSocketHandle,-1);
         if nSocketHandle < 0 then
            Continue;
         for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin
           if g_SessionArray[nSocketIndex].SocketHandle = nSocketHandle then begin
             g_SessionArray[nSocketIndex].MsgList.Add(sSocketMsg);
             break;
           end;
         end;
       end; //0x00452246
     end; //0x452252
     //if sProcessMsg <> '' then ClientSockeMsgList.Add(sProcessMsg);
     if sProcessMsg <> '' then sProcMsg:=sProcessMsg;

     nSendMsgCount:=0;
     n456A2C:=0;
     StringList318.Clear;
     for nSocketIndex:= 0 to GATEMAXSESSION - 1 do begin
       if g_SessionArray[nSocketIndex].SocketHandle <= -1 then Continue;

       //踢除超時無數據傳輸連接
       if (GetTickCount - g_SessionArray[nSocketIndex].dwConnctCheckTick) > dwKeepConnectTimeOut then begin
         sRemoteIPaddr:=g_SessionArray[nSocketIndex].sRemoteIPaddr;
         case BlockMethod of    //
           mDisconnect: begin
             g_SessionArray[nSocketIndex].Socket.Close;
           end;
           mBlock: begin
             New(IPaddr);
             IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
             TempBlockIPList.Add(IPaddr);
             CloseConnect(sRemoteIPaddr);
           end;
           mBlockList: begin
             New(IPaddr);
             IPaddr.nIPaddr:=inet_addr(PChar(sRemoteIPaddr));
             BlockIPList.Add(IPaddr);
             CloseConnect(sRemoteIPaddr);
           end;
         end;
         MainOutMessage('端口空連接攻擊: ' + sRemoteIPaddr,1);
         Continue;
       end;
       
       while (True) do begin;
         if g_SessionArray[nSocketIndex].MsgList.Count <= 0 then break;
         UserSession:=@g_SessionArray[nSocketIndex];
         nSendRetCode:=SendUserMsg(UserSession,UserSession.MsgList.Strings[0]);
         if (nSendRetCode >= 0) then
         begin
           if nSendRetCode = 1 then begin
             UserSession.dwConnctCheckTick:=GetTickCount();
             UserSession.MsgList.Delete(0);
             Continue;
           end;
           if UserSession.MsgList.Count > 100 then begin
             nMsgCount:=0;
             while nMsgCount <> 51 do begin
               UserSession.MsgList.Delete(0);
               Inc(nMsgCount);
             end;
           end;
           Inc(n456A2C,UserSession.MsgList.Count);
           MainOutMessage(UserSession.sIP +
                      ' : ' +
                      IntToStr(UserSession.MsgList.Count),5);
           Inc(nSendMsgCount);
         end else begin //0x004523A4
           UserSession.SocketHandle:= -1;
           UserSession.Socket:= nil;
           UserSession.MsgList.Clear;
         end;
       end;
     end;
     if (GetTickCount - dwSendKeepAliveTick) > 2 * 1000 then begin
       dwSendKeepAliveTick:=GetTickCount();
       if boGateReady then
         ClientSocket.Socket.SendText('%--$');
     end;
     if (GetTickCount - dwKeepAliveTick) > 10 * 1000 then begin
       boKeepAliveTimcOut:=True;
       ClientSocket.Close;
     end;
  finally
    boDecodeLock:=False;
  end;
  dwDecodeTime:=GetTickCount - dwDecodeTick;
  if dwDecodeMsgTime < dwDecodeTime then dwDecodeMsgTime:=dwDecodeTime;
  if dwDecodeMsgTime > 50 then Dec(dwDecodeMsgTime,50);
end;

 又是一坨代碼,簡而言之,就是把剛才保存接收到的數據。分發到 每個用戶的自己的消息鏈表中,然后遍歷,發送出去,
代碼如下:

// 發送用戶消息
function TFrmMain.SendUserMsg(UserSession:pTUserSession;sSendMsg:String):Integer;
begin
  Result:= -1;
  // 如果
  if UserSession.Socket <> nil then begin
    // 取反
    if not UserSession.bo0C then begin
      // 如果不能發送,則置可用
      if not UserSession.boSendAvailable and (GetTickCount > UserSession.dwSendLockTimeOut) then begin
          UserSession.boSendAvailable  := True;
          UserSession.nCheckSendLength := 0;
          boSendHoldTimeOut            := True;
          dwSendHoldTick               := GetTickCount();
      end; //004525DD
      if UserSession.boSendAvailable then begin
        if UserSession.nCheckSendLength >= 250 then begin
          if not UserSession.boSendCheck then begin
            UserSession.boSendCheck:=True;
            sSendMsg:='*' + sSendMsg;
          end;
          if UserSession.nCheckSendLength >= 512 then begin
            UserSession.boSendAvailable:=False;
            UserSession.dwSendLockTimeOut:=GetTickCount + 3 * 1000;
          end;
        end; //00452620
        UserSession.Socket.SendText(sSendMsg);
        Inc(UserSession.nSendMsgLen,length(sSendMsg));
        Inc(UserSession.nCheckSendLength,length(sSendMsg));
        Result:= 1;
      end else begin //0x0045264A
        Result:= 0;
      end;
    end else begin //0x00452651
      Result:= 0;
    end;
  end;
end;

 登錄網關,是不是很簡單,這不是 重點,重點是市面上的很多引擎的登錄網關都基於這套機制,只需要逆向分析下其加密算法,一個自定義網關則出來了。至於過濾規則,什么IP通道,都是浮雲。。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM