【问题标题】:Upgrading Delphi 7 Indy 9 app to Indy 10 (II)将 Delphi 7 Indy 9 应用程序升级到 Indy 10 (II)
【发布时间】:2016-10-22 17:52:31
【问题描述】:

这是我上一个问题here的后续问题。

许多命令和响应被编码为分隔字符串。 在 Delphi 7 中,这些通常使用 chr(166) 和 chr(167) 进行编码。

procedure TFormMain.IdTCPServer1InsertAccount(
  ASender: TIdCommand);
var
  cmd: String;
  request: String;
  Params: TMyStrings;
  AccountNo, Address, UserName: String;
begin
  cmd := 'InsertAccount';
  request := Copy(ASender.Rawline, Length(cmd) + 2, Length(ASender.RawLine));
  Params := TMyStrings.Create;
  try
    AssignDelimited(chr(166), request, Params);
    AccountNo := Params[0];
    Address := replace(char(167), #13#10, Params[1])
    UserName := Params[2];

似乎这样做是为了让参数可以包含空格。同样,内容来自备忘录控件的命令将其回车换行符替换为 chr(167),因此可以在不终止命令的情况下发送备忘录内容:

// typical client code
request := edAccountNo.Text + chr(166) + 
  replace(#13, chr(167), replace(#10, '', memoAddress.Lines.Text) + 
  chr(166) + Fusername;

idTCPClient1.WriteLn('InsertAccount' + space + request);

现在,在使用 Indy 10 将此代码转换为 Delphi 10.1 时,我使用 ANSIChar(166) 进行了搜索和替换 chr(166),但我很快发现 Indy 10 不“喜欢”高于 127 的 ANSIChar . 请求在客户端看起来是正确的,但在服务器上接收到 ?'s

升级此代码的最佳方法是什么? 谢谢。

【问题讨论】:

    标签: delphi indy


    【解决方案1】:

    Indy 10 是UnicodeString-aware,而 Indy 9 不是。 Delphi 2009 及更高版本使用UnicodeString 作为其原生string 类型,而Delphi 2007 及更早版本使用AnsiString

    Indy 9 将AnsiString 数据按原样作为 8 位数据传输。 Indy 10 使用字符集转换将AnsiString/UnicodeString 字符转换为字节,然后传输字节。

    Indy 10 的默认字符集是 ASCII,其中任何高于 U+007F 的 Unicode 字符都将转换为 0x3F。您使用大于 U+007F 的字符作为参数分隔符,因此默认 ASCII 字符集将它们转换为 ?,从而破坏了您的协议​​。使用 ASCII 控制字符

    要在不更改协议的情况下解决此问题,您可以将 Indy 10 设置为使用其内置的 8 位字符集进行字符串 字节转换(只要您不需要发送 Unicode 字符 > U+00FF在您的协议中)。为此,您可以:

    1. 在客户端连接到您的服务器后,将连接的IOHandler.DefStringEncoding 属性设置为IndyTextEncoding_8Bit。在连接的客户端和服务器端都这样做:

      procedure TFormMain.IdTCPServer1Connect(AContext: TIdContext);
      begin
        AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
      end;
      

      idTCPClient1.Connect;
      idTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
      
    2. 将 Indy 的全局 GIdDefaultTextEncoding 变量在 IdGlobal 单元中设置为 enc8Bit

      procedure TFormMain.FormCreate(Sender: TObject);
      begin
        GIdDefaultTextEncoding := enc8Bit;
      end;
      
    3. 在客户端调用IOHandler.WriteLn()时,可以在其可选的AByteEncoding参数中传递IndyTextEncoding_8Bit

      idTCPClient1.IOHandler.WriteLn('InsertAccount' + space + request, IndyTextEncoding_8Bit);
      

      在服务器端,最好分配连接的IOHandler.DefStringEncoding 属性,或者至少设置GIdDefaultTextEncoding 变量。但是,作为替代方案,您可以从TIdCmdTCPServer 派生一个新组件(甚至使用插入器类)并覆盖其虚拟ReadCommandLine() 方法以调用连接的IOHandler.ReadLn() 方法,在其可选IndyTextEncoding_8Bit 中指定AByteEncoding参数:

      type
        TIdCmdTCPServer = class(IdCommandHandlers.TIdCmdTCPServer)
        protected
          function ReadCommandLine(AContext: TIdContext): string; override;
        end;
      
        TFormMain = class(TForm)
          IdTCPServer1: TIdCmdTCPServer;
          ...
        end;
      
        ...
      
        function TIdCmdTCPServer.ReadCommandLine(AContext: TIdContext): string;
        begin
          Result := AContext.Connection.IOHandler.ReadLn(IndyTextEncoding_8Bit);
        end;
      

    仅供参考,顺便说一句,TCommandHandler 有一个ParamDelimiter 属性。如果您将其设置为#166(默认为#32)并将ParseParams 设置为True,您可以删除您的AssignDelimited() 函数并让TIdCommandHandler 在触发之前将您的分隔参数解析为TIdCommand.Params 属性它的OnCommand 事件。

    甚至可以更进一步,从TIdCommandHandler 派生一个新类并覆盖其虚拟DoParseParams() 方法来处理#167 -> CRLF 转换,而不是在每个OnCommand 事件处理程序中手动执行此操作.

    【讨论】:

    • 感谢您一直为我的问题提供完整的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-02
    • 2011-03-13
    • 1970-01-01
    • 2014-07-14
    • 1970-01-01
    • 2015-09-14
    • 2012-07-12
    相关资源
    最近更新 更多