【问题标题】:Upgrading Delphi 7 Indy 9 app. to Indy 10升级 Delphi 7 Indy 9 应用程序。到印地 10
【发布时间】:2016-10-19 02:48:49
【问题描述】:

我继承了一个扩展的(199 个命令)Delphi 7 Indy 9 应用程序,我正在升级到 Indy 10(在 D10.1 中)。我已经升级了所有代码,它可以编译并运行。我遇到的问题是,现在在 Indy 10 中,除了在 Indy 9 下所做的编码响应之外,所有处理程序还返回响应代码(和文本)。

例如:

// server 
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
  Rights: String;
begin

   if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
   begin
      myClient := TClientData.Create;
      myClient.ClientName := ASender.Params[0];
      myClient.ClientHost := #32; // indy9 was .Thread.Connection.LocalName; 
      myClient.ID := Now;
      ASender.Context.Data := myClient;

      ListBox1.Items.AddObject(
        PadR(myClient.ClientName,12,' ') + '=' +
        FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
        TDateTimeO.Create(myClient.ID));
      ASender.Context.Connection.IOHandler.WriteLn('SUCCESS' + ' ' + Rights)  
  end
  else
      ASender.Context.Connection.IOHander.WriteLn('Login failed!');

end;

...

// client side
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
var
  response, response1: String;
begin

  frmMain.IdTCPClient1.IOHandler.WriteLn('login' + ' ' + 
     username + ' ' + password)
  response := frmMain.IdTCPClient1.IOHandler.ReadLn();
  // I have to add this now to capture the response code too!
  response1 := frmMain.IdTCPClient1.IOHandler.ReadLn(); // 200 OK
  // ------------------------------------------------------
  if Copy(response,1,7) = 'SUCCESS' then
  begin
    rights := Copy(response,9,4);

有很多命令处理程序,它们都有自己的自定义响应。在客户端需要更改很多代码。如果命令处理程序已经提供了自己的响应,有没有办法告诉IdCmdTCPServer 抑制标准的“200 Ok”响应?还是我要度过一个漫长的夜晚?

谢谢

【问题讨论】:

    标签: delphi indy10


    【解决方案1】:

    如果您需要抑制默认命令响应,您可以:

    1. 清除TIdCommandHandlerReplyNormalExceptionReply 属性(这也适用于Indy 9,除了ExceptionReply 在那个版本中是ReplyExceptionCode)和服务器的CommandHandlers.ExceptionReply 属性(仅限 Indy 10)。

    2. 在您的 OnCommand 处理程序中将 TIdCommand.PerformReply 属性设置为 false(这也适用于 Indy 9):

      procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
      var
        ...
      begin
        ASender.PerformReply := False;
        ...
      end;
      
    3. 将服务器的 CommandHandlers.PerformReplies 属性设置为 false(仅限 Indy 10 - 默认情况下将 TIdCommand.PerformReply 设置为 false):

      IdCmdTCPServer1.CommandHandlers.PerformReplies := False;
      

    另一方面,您应该考虑按照设计使用的方式使用命令处理程序响应,例如:

    procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
    var
      Rights: String;
    begin
      if ASender.Params.Count = 2 then
      begin
        if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
        begin
          ...
          ASender.Reply.SetReply('SUCCESS', Rights);
        end
        else
          ASender.Reply.SetReply('ERROR', 'Login failed!');
      end
      else
        ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
    end;
    

    我什至会说您应该将TIdCommandHandler.NormalReply.Code 属性设置为SUCCESS 并将TIdCommandHandler.ExceptionReply.Code 属性设置为ERROR,然后您可以在OnCommand 处理程序中执行此操作:

    procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
    var
      Rights: String;
    begin
      if ASender.Params.Count <> 2 then
        raise Exception.Create('Wrong number of parameters!');
    
      if not BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
        raise Exception.Create('Login failed!');
    
      ...
    
      ASender.Text.Text := Rights;
    end;
    

    话虽如此,这些方法中的任何一种都应该可以在不更改现有客户端代码的情况下正常工作。但是,在 Indy 10 中,我建议直接使用 SendCmd() 而不是 WriteLn()/ReadLn()

    function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
    var
      response: String;
    begin
      response := frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password);
      if response = 'SUCCESS' then
      begin
        rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
        ...
      end else begin
        // error message in frmMain.IdTCPClient1.LastCmdResult.Text.Text ...
      end;
    end;
    

    或者,您可以让SendCmd() 在未收到SUCCESS 回复时引发异常:

    function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
    begin
      try
        frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password, 'SUCCESS');
      except
        on E: EIdReplyRFCError do begin
          // error message in E.Message ...
          ...
          Exit;
        end;
      end;
    
      rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
      ...
    end;
    

    SendCmd() 确实存在于 Indy 9 中,但它只支持基于数字的响应代码,您没有使用它。正如您在上面看到的,Indy 10 中的SendCmd() 支持基于字符串的响应代码以及数字响应代码。


    附带说明:在您的服务器代码中,OnCommand 处理程序在工作线程中运行,因此您对ListBox1.Items.AddObject() 的使用不是线程安全的。对 UI 的任何访问必须与主 UI 线程同步,使用 TThread.Synchronize()TThread.Queue()TIdSyncTIdNotify 等技术,例如:

    procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
    var
      Rights: String;
      myClient: TClientData;
    begin
      if ASender.Params.Count = 2 then
      begin
        if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
        begin
          myClient := TClientData(ASender.Context.Data);
          if myClient = nil then
          begin
            myClient := TClientData.Create;
            ASender.Context.Data := myClient;
          end;
    
          myClient.ID := Now;
          myClient.ClientName := ASender.Params[0];
    
          myClient.ClientHost := GStack.HostByAddress(ASender.Context.Binding.PeerIP, ASender.Context.Binding.IPVersion);
          // In Indy 9, this would be:
          // myClient.ClientHost := GStack.WSGetHostByAddr(ASender.Thread.Connection.Socket.PeerIP);
          // NOT ASender.Thread.Connection.LocalName!
    
          TThread.Queue(nil,
            procedure
            begin
              ListBox1.Items.AddObject(
                PadR(myClient.ClientName,12,' ') + '=' + FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
                TDateTimeO.Create(myClient.ID));
            end
          );
    
          ASender.Reply.SetReply('SUCCESS', Rights);
        end
        else
          ASender.Reply.SetReply('ERROR', 'Login failed!');
      end
      else
        ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
    end;
    

    确保您的 BillingUserRegistered() 函数同样是线程安全的(如果还不是的话)。

    【讨论】:

    • 出色的答案。如果你还没有写书,你打算什么时候写?
    • 是的,我同意。这是一个非常完整的答案,我很感激。 Delphi 社区会发现 Indy 指南非常有用。在那之前,我们有 SO。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-02
    • 2011-03-13
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    相关资源
    最近更新 更多