将 Delphi 7 Indy 9 应用程序升级到 Indy 10 (II)

Upgrading Delphi 7 Indy 9 app to Indy 10 (II)

这是我之前问题 的后续问题。

很多命令和响应都被编码为分隔字符串。 在 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];

这样做似乎是为了让参数可以包含空格。类似地,内容来自备忘录控件的命令将其回车 return 换行符替换为 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 没有 "like" ANSIChars 高于 127。请求在客户端看起来是正确的,但在服务器端收到时带有 ?'s

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

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 控制字符 < U+0020 会更安全,例如 U+0001.

要在不更改协议的情况下解决此问题,您可以将 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. IdGlobal单元中Indy的全局GIdDefaultTextEncoding变量设置为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 派生一个新组件(或者甚至使用插入器 class)并覆盖其虚拟 ReadCommandLine() 方法以调用连接的 IOHandler.ReadLn() 方法在其可选的 AByteEncoding 参数中指定 IndyTextEncoding_8Bit

    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 解析您的在触发其 OnCommand 事件之前将分隔参数放入 TIdCommand.Params 属性。

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