You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将C# Socket XML接口请求代码迁移至Delphi时无响应求助

C# Socket代码迁移至Delphi Indy TIdTCPClient无响应问题排查

我正尝试将C# Socket代码迁移至Delphi,使用Indy的TIdTCPClient组件,但始终无法获取响应,直到超时。以下是原C#代码及我编写的Delphi代码,请求协助排查问题。

原C#代码

public static string SendXML(String xmlMessage, int cashbackAmount, bool jumpOut = false)
{
    connection:
    try
    {
        string messageToSend = xmlMessage;
        String responseStringData = "";
        int headerOffset = 2;
        var length = messageToSend.Length;
        byte[] sendbytes = new byte[length + 2];
        byte[] responsebytes = new byte[1024 * 10];
        int remainder = 0;
        var bytesRec = 0;
        if (length < 65535)
        {
            sendbytes[0] = Convert.ToByte(Math.DivRem(length, 256, out remainder));
            sendbytes[1] = Convert.ToByte(remainder);
        }
        else
        {
            headerOffset = 6;
            sendbytes[0] = 0xFF;
            sendbytes[1] = 0xFF;

            byte[] intBytes = BitConverter.GetBytes(length);
            Array.Reverse(intBytes);
            byte[] result = intBytes;
            Array.Copy(result, 0, sendbytes, 2, 4);
        }

        try
        {
            IPHostEntry host = Dns.GetHostEntry("localhost");
            IPAddress ipAddress = host.AddressList[0];
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, 23001);

            Socket s = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(remoteEP);
            s.ReceiveTimeout = TimeSpan.FromSeconds(30).Milliseconds;
            s.SendTimeout = TimeSpan.FromSeconds(30).Milliseconds;

            byte[] msg = Encoding.ASCII.GetBytes(messageToSend);
            Array.Copy(msg, 0, sendbytes, headerOffset, msg.Length);
            int bytesSent = s.Send(sendbytes);

            bool isEvent = true;
            bool didContainEvent = false;
            bool wasCashbackEvent = false;
            bytesRec = 0;
            while (isEvent)
            {
                bytesRec = s.Receive(responsebytes);

                responseStringData = Encoding.ASCII.GetString(responsebytes, 0, bytesRec);
                responseStringData = responseStringData.Substring(2, responseStringData.Length - 2);
                responseStringData = responseStringData.Replace("\0?", "");
                Console.WriteLine("Data Received = {0}", responseStringData);
                Console.WriteLine();
                Console.WriteLine();
                isEvent = responseStringData.Contains("Esp:Event") | responseStringData.Contains("Esp:Callback");
                if (isEvent)
                {
                    if (jumpOut)
                    {
                        return responseStringData;
                    }
                    didContainEvent = true;
                    if (!responseStringData.Contains("Esp:Callback"))
                        continue;
                    wasCashbackEvent = true;
                    string cashbackMessage = $"<?xml version=\"1.0\" encoding=\"UTF-8\"?><Esp:Interface Version=\"1.0\" xmlns:Esp=\"http://www.mosaicsoftware.com/Postilion/eSocket.POS/\"><Esp:Callback TerminalId=\"TR000002\" EventId=\"DATA_REQUIRED\" ResponseData=\"{cashbackAmount}\" /></Esp:Interface>";
                    MessageBox.Show(responseStringData);
                    SendXML(cashbackMessage, 0, jumpOut: true);
                    byte[] cashMsg = Encoding.ASCII.GetBytes(cashbackMessage);
                    Array.Copy(cashMsg, 0, sendbytes, headerOffset, cashMsg.Length);
                    s.Send(sendbytes);
                }
                return responseStringData;
            }
        }
        catch (Exception)
        {
            return responseStringData;
        }
        return responseStringData;
    }
    catch (Exception)
    {
        goto connection;
    }
}

我的Delphi代码

function TfrmEFT000.SendXML(xXMLMessage :String; xCashbackAmount :Integer = 0; wJumpOut :Boolean = False): string;
label Connection;
var
  wResponseStringData                                     :String;
  wHeaderOffset, wLength, wRemainder, wByteInt            :Word;
  wBytesRec, wBytesSent                                   :Integer;
  wSendBytes, wResponseBytes, wMsg, wResult               :TIdBytes;
  wIPAddress                                              :TIPAddress;
  wHost : PHostEnt;
  ipAddr : array of TIPAddress;

  wIsEvent, wDidContainEvent, wWasCashbackEvent           :Boolean;
begin

  try
    wHeaderOffset := 2;
    wLength       := Length(xXMLMessage);

    SetLength(wSendBytes, wLength+2);
    SetLength(wResponseBytes, 1024 * 10);
    wRemainder := 0;
    wBytesRec  := 0;

    DivMod(wLength, 264, wByteInt, wRemainder);

    if (wLength < 65535) then
    begin
      wSendBytes[0] := Byte(wByteInt);
      wSendBytes[1] := Byte(wRemainder);
    end
    else
    begin
      wHeaderOffset := 6;
      wSendBytes[0] := $FF;
      wSendBytes[1] := $FF;

//    wIntBytes := BitConverter.GetBytes(wLength);
//    Array.Reverse(wIntBytes);
//    byte[] result = wIntBytes;
//    Array.Copy(result, 0, sendbytes, 2, 4);

    end;

    TCPClient.IOHandler.WriteBufferOpen;
    try

      wMsg := IndyTextEncoding_ASCII.GetBytes(xXMLMessage);

      Assert(Length(wMsg)<=Length(wSendBytes));
      Move(wMsg[0], wSendBytes[2], Length(wMsg));

      TCPClient.IOHandler.Write(wSendBytes,Length(wSendBytes),2);
      TCPClient.IOHandler.WriteBufferClose;

      wIsEvent          := True;
      wDidContainEvent  := False;
      wWasCashbackEvent := False;

      while wIsEvent do begin

        Connection:

        TCPClient.Socket.CheckForDataOnSource(255);
        wResponseStringData := TCPClient.IOHandler.ReadLnWait(30000,IndyTextEncoding_ASCII);

        if (not TCPClient.Socket.InputBufferIsEmpty) then
          wResponseStringData := TCPClient.Socket.InputBufferAsString(IndyTextEncoding_ASCII);

        if wResponseStringData = '' then begin
          goto Connection;
        end;

        wResponseStringData := IndyTextEncoding_ASCII.GetString(wResponseBytes,0,wBytesRec);
        wResponseStringData := wResponseStringData.Substring(2, wResponseStringData.Length - 2);
        wResponseStringData := wResponseStringData.Replace('\0?', '');
        LogMessage(lmtInformation, ldIn, 'Data Received = {0}');
        LogMessage(lmtInformation, ldIn, wResponseStringData);
        LogMessage(lmtInformation, ldIn, '');
        LogMessage(lmtInformation, ldIn, '');

      end;

    except
      on E:Exception do begin
        LogMessage(lmtError, ldIn, E.Message);
        ShowMessage(E.Message);
        Exit;
      end;
    end;

  except
    on E:Exception do begin
      TCPClient.IOHandler.WriteBufferCancel;
      LogMessage(lmtError, ldIn, E.Message);
      ShowMessage(E.Message);
      //goto Connection;
    end;
  end;

end;

问题排查与修正方案

核心问题点

  • 长度计算错误:C#中用256作为除数拆分长度高位/低位,Delphi误用264,导致头部长度标识错误,服务端无法解析消息。
  • 长消息头部未实现:长度≥65535时,Delphi未写入4字节大端格式的长度值,导致消息不完整。
  • 发送偏移错误:写入消息内容时,固定用偏移2,未根据wHeaderOffset动态调整,长消息会覆盖头部数据。
  • 接收逻辑完全错误:原协议是二进制字节流,却用ReadLnWait按行读取;未实际读取字节到wResponseBytes,wBytesRec始终为0,导致后续字符串处理无效;Delphi字符串索引从1开始,Substring用法错误;转义字符串\0?无法匹配ASCII空字符。
  • 连接逻辑缺失:未处理TIdTCPClient的连接流程,若组件未提前连接,发送会失败。
  • 递归调用风险:原C#递归调用SendXML会新建Socket,但Delphi若用全局TIdTCPClient,递归会导致连接冲突,应复用当前连接。

修正后的Delphi代码

function TfrmEFT000.SendXML(xXMLMessage: String; xCashbackAmount: Integer = 0; wJumpOut: Boolean = False): string;
label Connection;
var
  wResponseStringData: String;
  wHeaderOffset, wLength: Integer;
  wRemainder: Word;
  wBytesRec, wBytesSent: Integer;
  wSendBytes, wResponseBytes, wMsg: TIdBytes;
  wIsEvent, wDidContainEvent, wWasCashbackEvent: Boolean;
  wCashbackMessage: String;
  wIntBytes: array[0..3] of Byte;
begin
  Result := '';
Connection:
  try
    wHeaderOffset := 2;
    wLength := Length(xXMLMessage);

    // 初始化发送缓冲区:短消息长度+2,长消息长度+6
    if wLength < 65535 then
      SetLength(wSendBytes, wLength + 2)
    else
      SetLength(wSendBytes, wLength + 6);

    // 构建消息头部
    if wLength < 65535 then
    begin
      // 拆分长度为高位(256)和低位
      wSendBytes[0] := Byte(wLength div 256);
      wSendBytes[1] := Byte(wLength mod 256);
    end
    else
    begin
      wHeaderOffset := 6;
      wSendBytes[0] := $FF;
      wSendBytes[1] := $FF;
      // 将长度转为大端4字节
      wIntBytes[3] := Byte(wLength and $FF);
      wIntBytes[2] := Byte((wLength shr 8) and $FF);
      wIntBytes[1] := Byte((wLength shr 16) and $FF);
      wIntBytes[0] := Byte((wLength shr 24) and $FF);
      Move(wIntBytes[0], wSendBytes[2], 4);
    end;

    // 写入XML消息内容
    wMsg := IndyTextEncoding_ASCII.GetBytes(xXMLMessage);
    Move(wMsg[0], wSendBytes[wHeaderOffset], Length(wMsg));

    // 确保连接到服务端
    if not TCPClient.Connected then
    begin
      TCPClient.Host := 'localhost';
      TCPClient.Port := 23001;
      TCPClient.ReadTimeout := 30000;
      TCPClient.Connect;
    end;

    // 发送消息
    TCPClient.IOHandler.Write(wSendBytes);

    // 接收响应
    wIsEvent := True;
    wDidContainEvent := False;
    wWasCashbackEvent := False;
    SetLength(wResponseBytes, 1024 * 10);

    while wIsEvent do
    begin
      // 读取响应字节
      wBytesRec := TCPClient.IOHandler.ReadBytes(wResponseBytes, -1, False);
      if wBytesRec <= 0 then
        Break;

      // 转换为字符串并处理头部
      wResponseStringData := IndyTextEncoding_ASCII.GetString(wResponseBytes, 0, wBytesRec);
      // Delphi字符串索引从1开始,跳过前2字节
      if Length(wResponseStringData) > 2 then
        wResponseStringData := Copy(wResponseStringData, 3, Length(wResponseStringData) - 2)
      else
        wResponseStringData := '';

      // 替换ASCII空字符+问号
      wResponseStringData := StringReplace(wResponseStringData, #0'?', '', [rfReplaceAll]);

      LogMessage(lmtInformation, ldIn, 'Data Received = ' + wResponseStringData);
      LogMessage(lmtInformation, ldIn, '');

      // 判断是否为事件/回调
      wIsEvent := (Pos('Esp:Event', wResponseStringData) > 0) or (Pos('Esp:Callback', wResponseStringData) > 0);
      if wIsEvent then
      begin
        if wJumpOut then
        begin
          Result := wResponseStringData;
          Exit;
        end;
        wDidContainEvent := True;

        // 处理回调消息
        if Pos('Esp:Callback', wResponseStringData) > 0 then
        begin
          wWasCashbackEvent := True;
          wCashbackMessage := '<?xml version="1.0" encoding="UTF-8"?><Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/"><Esp:Callback TerminalId="TR000002" EventId="DATA_REQUIRED" ResponseData="' + IntToStr(xCashbackAmount) + '" /></Esp:Interface>';
          // 复用当前连接发送回调响应
          SendXML(wCashbackMessage, 0, True);
        end
        else
          Continue;
      end;
      // 返回非事件响应
      Result := wResponseStringData;
      Break;
    end;
  except
    on E: Exception do
    begin
      LogMessage(lmtError, ldIn, E.Message);
      ShowMessage(E.Message);
      // 断开连接,重试一次
      if TCPClient.Connected then
        TCPClient.Disconnect;
      goto Connection;
    end;
  end;
end;

内容的提问来源于stack exchange,提问作者Ntsito Maelane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 14:25:24