Mir2源码详解之服务端-登录网关(LoginGate)

传奇这款游戏,一直对我的影响很大。当年为了玩传奇,逃课,被老师叫过N次家长。言归正传,网上有很多源码,当然了,都是delphi的。并且很多源码还不全,

由于一直学习的c、c++。delphi还真不懂。无奈硬着头皮上。好了。废话不多说。开始。

 

登录网关,负责游戏最开始的登录处理(与账户服务器LoginSvr通讯)。验证登录器输入的账户密码是否正确。

Mir2源码详解之服务端-登录网关(LoginGate)

界面上的控件很多。其实干活的就 就 三个:“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通道,都是浮云。。。。

 

你可能感兴趣的:(login)