• 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通道,都是浮云。。。。

  • 相关阅读:
    Kafka介绍
    测试Random类nextInt()方法连续两次结果一样的概率
    Java LinkedHashMap学习
    AES加密
    DES加密
    Windows访问VirtualBox的Redis服务器
    ubuntu配置JDK环境
    初学Python
    commons Collections4 MultiMap
    Guava bimap
  • 原文地址:https://www.cnblogs.com/ziolo/p/4624871.html
Copyright © 2020-2023  润新知