Delphi 中通过套接字发送XML接口请求

yfjy0ee7  于 2023-02-15  发布在  其他
关注(0)|答案(2)|浏览(202)

我试图将我的C#套接字代码复制到 Delphi 中,但是我很难从我尝试的例程中获得任何响应。
我尝试使用Indy的TIdTCPClient组件,但直到超时才得到任何返回。
我想在 Delphi 中重新创建以下C#代码:

public static string SendXML(String xmlMessage, int cashbackAmount, bool jumpOut = false)
{
    connection:
    try
    {
        string messageToSend = xmlMessage;
        String responseStringData = "";
        // String responseString = "";
        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);
        }

        // Connect the socket to the remote endpoint. Catch any errors.
        try
        {
            // Connect to a Remote server
            // Get Host IP Address that is used to establish a connection
            // In this case, we get one IP address of localhost that is IP : 127.0.0.1
            // If a host has multiple addresses, you will get a list of addresses
            // var connectionDetails = new Dictionary<String, String> { { "Host", HostName }, { "Port", Port.ToString() } };
            // Console.WriteLine("Attempting socket connection {0}", connectionDetails);
            IPHostEntry host = Dns.GetHostEntry("localhost");
            IPAddress ipAddress = host.AddressList[0];
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, 23001);

            // Create a TCP/IP  socket.
            Socket s = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            // Connect to Remote EndPoint
            s.Connect(remoteEP);
            s.ReceiveTimeout = TimeSpan.FromSeconds(30).Milliseconds;
            s.SendTimeout = TimeSpan.FromSeconds(30).Milliseconds;

            // Encode the data string into a byte array.
            byte[] msg = Encoding.ASCII.GetBytes(messageToSend);
            Array.Copy(msg, 0, sendbytes, headerOffset, msg.Length);
            // Send the data through the socket.
            int bytesSent = s.Send(sendbytes);

            // Receive the response from the remote device.
            bool isEvent = true;
            bool didContainEvent = false;
            bool wasCashbackEvent = false;
            bytesRec = 0;
            // since we are still connected, we can receive quite a few event responses and/or callbacks
            // we cannot close this IP connection until all data has come back to us, else this will cause issues
            // as for callbacks, they require a response, in this case we have to keep the IP connection open until
            // we have replied with a callback message
            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;
                    // Adding Cashback Callback Response Message
                    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>";
                    // Sending the cashback message with cashback amount to the callback
                    MessageBox.Show(responseStringData);
                    SendXML(cashbackMessage, 0, jumpOut: true);
                    // Encode the cashback data string into a byte array.
                    byte[] cashMsg = Encoding.ASCII.GetBytes(cashbackMessage);
                    Array.Copy(cashMsg, 0, sendbytes, headerOffset, cashMsg.Length);
                    // Send the data through the socket.
                    s.Send(sendbytes);
                }
                // Return the response 
                return responseStringData;
            }
        }
        catch (Exception)
        {
            // txtDisplayMessage.Text = "Failed to connect to socket...";
            return responseStringData;
            // goto connection;
        }
        return responseStringData;
    }
    catch (Exception)
    {
        // txtDisplayMessage.Text = "Failed to connect to socket...";
        goto connection;
        // return responseStringData;
    }
    // string responseString = null;
    // return responseStringData;
}

我的 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;
  //wRemoteEP                                               :PIPE_WAIT=
  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;

      // Receive the response from the remote device.
      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;

        //TCPClient.IOHandler.ReadBytes(wResponseBytes, wBytesRec);

        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;
kognpnkq

kognpnkq1#

我发现您的 Delphi 代码存在许多问题:

  • 您在多个调用中重用了一个TCP连接,而C#代码在每个调用中,甚至在外部循环的每个迭代中,都创建了一个新的TCP连接。
  • 您的代码假设wLength始终〈65535。您不允许更大的消息。如果wLength碰巧〉= 65535,则您根本没有将wLength复制到wSendBytes中,也没有将wMsg复制到wSendBytes的正确偏移量中。因此wSendBytes将被损坏。
  • 您没有发送放入wSendBytes中的所有字节。您跳过了前2个字节。此语句:

TCPClient.IOHandler.Write(wSendBytes,Length(wSendBytes),2);
需要改为:
TCPClient.IOHandler.Write(wSendBytes);

  • 不需要调用IOHandler.WriteBuffer(Open|Close|Cancel),因为您在它们之间只进行了1次IOHandler.Write()调用,因此不需要缓冲数据。
  • 您没有处理IOHandler.ReadLnWait()超时的可能性,这与C#代码处理s.Receive()超时的方式相同。此外,C#代码中甚至没有指示IOHandler.ReadLnWait()是用于 Delphi 代码的正确方法。C#代码阅读的是任意字节,而不是基于行的文本数据。
  • 就此而言,假设响应数据是XML,您应该从套接字阅读,直到收到XML文档的最后一个结束标记,然后处理响应(基于推的XML解析器最适合于此。您可以将任意字节推到其中,并且它将为完全解析的XML元素给予事件)。 Delphi 代码假设整个XML文档将出现在套接字的单个读取中,但这不是TCP的工作方式。
  • 在接收到wResponseStringData之后,但在处理它之前,您将其覆盖。首先,您使用IOHandler.ReadLnWait()接收它,但随后您再次从IndyTextEncoding_ASCII.GetString(wResponseBytes)接收它,尽管您已经注解掉了将字节读入wResponseBytes的代码。因此,每次处理它时,wResponseStringData最终都是空的。
  • 您根本不会像C#代码那样响应任何接收到的事件(另外,仅供参考,C#代码发送cashbackMessage两次,每次都在不同的TCP连接上,这看起来很奇怪。但更重要的是,它没有调整sendbytes的报头以指定cashbackMessage的正确长度)。

也就是说,我会将C#代码翻译成 Delphi +Indy,更像下面这样:

uses
  ..., IdGlobal, IdStack, IdExceptionCore, IdTCPClient, SysUtils;
  
function SendXML(const xmlMessage: String; cashbackAmount: Integer; jumpOut: Boolean = False): String;
var
  responseStringData: String;
  responsebytes: TIdBytes;
  bytesRec: Integer;
  client: TIdTCPClient;
  isEvent: Boolean;
  didContainEvent: Boolean;
  wasCashbackEvent: Boolean;
  cashbackMessage: String;

  function EncodeMessage(const AMsg: String): TIdBytes;
  var
    length: Integer;
    headerOffset: Integer;
  begin
    length := IndyTextEncoding_ASCII.GetByteCount(AMsg);
    if length < 65535 then
    begin
      SetLength(Result, length + 2);
      CopyTIdUInt16(GStack.HostToNetwork(UInt16(length)), Result, 0);
      headerOffset := 2;
    end
    else
    begin
      SetLength(Result, length + 6);
      Result[0] := $FF;
      Result[1] := $FF;
      CopyTIdUInt32(GStack.HostToNetwork(UInt32(length)), Result, 2);
      headerOffset := 6;
    end;
    CopyTIdString(AMsg, Result, headerOffset, length, IndyTextEncoding_ASCII);
  end;

begin
  SetLength(responsebytes, 1024 * 10);

  // Create a TCP/IP socket.
  client := TIdTCPClient.Create;
  try
    client.Host := 'localhost';
    client.Port := 23001;
    client.ReadTimeout := 30000;

    repeat
      try
        responseStringData := '';
        //responseString := '';

        // Connect the socket to the remote endpoint. Catch any errors.
        try
          // Connect to a Remote server
          // WriteLn('Attempting socket connection, Host: ', client.Host, ', Port: ', client.Port);

          // Connect to Remote EndPoint
          client.Connect;
          try
            // Encode the data string into a byte array.
            // Send the data through the socket.
            client.IOHandler.Write(EncodeMessage(xmlMessage));

            // Receive the response from the remote device.
            isEvent := True;
            didContainEvent := False;
            wasCashbackEvent := False;

            // since we are still connected, we can receive quite a few event responses and/or callbacks
            // we cannot close this IP connection until all data has come back to us, else this will cause issues
            // as for callbacks, they require a response, in this case we have to keep the IP connection open until
            // we have replied with a callback message
            repeat
              client.IOHandler.CheckForDataOnSource(s.ReadTimeout);
              client.IOHandler.CheckForDisconnect;
              if client.IOHandler.InputBufferIsEmpty then EIdReadTimeout.Create('');

              bytesRec := IndyMin(client.IOHandler.InputBuffer.Size, Length(responsebytes));
              if bytesRec > 0 then begin
                client.IOHandler.ReadBytes(responsebytes, bytesRec, False);
              end;

              responseStringData := IndyTextEncoding_ASCII.GetString(responsebytes, 0, bytesRec);
              responseStringData := Copy(responseStringData, 2, Length(responseStringData));
              responseStringData := StringReplace(responseStringData, #0'?', '', [rfReplaceAll]);

              WriteLn('Data Received = ', responseStringData);
              WriteLn;
              WriteLn;

              isEvent := (Pos('Esp:Event', responseStringData) <> 0) or (Pos('Esp:Callback', responseStringData) <> 0);
              if isEvent and (not jumpOut) then
              begin
                didContainEvent := True;
                if Pos('Esp:Callback', responseStringData) = 0 then
                  Continue;
                wasCashbackEvent := True;

                // Adding Cashback Callback Response Message
                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="' + IntToStr(cashbackAmount) + '" /></Esp:Interface>';
                // Sending the cashback message with cashback amount to the callback
                ShowMessage(responseStringData);

                //SendXML(cashbackMessage, 0, True);

                // Encode the cashback data string into a byte array.
                // Send the data through the socket.
                client.IOHandler.Write(EncodeMessage(cashbackMessage));
              end;
              // Return the response 
              Result := responseStringData;
              Exit;
            until not isEvent;
          finally
            client.Disconnect;
            if client.IOHandler <> nil then begin
              client.IOHandler.InputBuffer.Clear;
            end;
          end;
        except
          // txtDisplayMessage.Text := 'Failed to connect to socket...';
          Result := responseStringData;
          Exit;
        end;
        // Continue;
        Result := responseStringData;
        Exit;
      except
        // txtDisplayMessage.Text := 'Failed to connect to socket...';
        Continue;
        // Result := responseStringData;
      end;
    until False;
  finally
    client.Free;
  end;
  // Result := '';
end;
hpcdzsge

hpcdzsge2#

我设法复制了它。如果有人需要它,下面是答案。

{------------------------------------------------------------------------------}
function TfrmEFT000.SendXML(xXMLMessage :String; xCashbackAmount :Integer = 0; xJumpOut :Boolean = False): string;
var
 wRespData, wCBReq                                                 :String;
 wHeaderOffset, wLength, wRemainder, wByteInt                      :Word;
 wBytesRec, wBytesSent                                             :Integer;
 wSendBytes, wResponseBytes, wMsg, wIntBytes, wResult, wCashMsg    :TIdBytes;
 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, 256, 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 := GetBytes(wLength);
    ReverseArray(wIntBytes);
    wResult := wIntBytes;

    CopyTIdBytes(wResult, 0, wSendBytes, 2, 4);
   end;

  try

   // Encode the data string into a byte array.
   wMsg := IndyTextEncoding_ASCII.GetBytes(xXMLMessage);
   CopyTIdBytes(wMsg, 0, wSendBytes, wHeaderOffset, Length(wMsg));

   // Send the data through the socket.
   SendToSocket(wSendBytes);

   wBytesSent := Length(wSendBytes);

   // Receive the response from the remote device.
   wIsEvent          := True;
   wDidContainEvent  := False;
   wWasCashbackEvent := False;
   wBytesRec         := 0;
   // since we are still connected, we can receive quite a few event responses and/or callbacks
   // we cannot close this IP connection until all data has come back to us, else this will cause issues
   // as for callbacks, they require a response, in this case we have to keep the IP connection open until
   // we have replied with a callback message
   while wIsEvent = True do begin

    TCPClient.Socket.CheckForDataOnSource(1000);

    wBytesRec := TCPClient.Socket.InputBuffer.Size;

    if (not TCPClient.Socket.InputBufferIsEmpty) then begin
     wRespData := TCPClient.Socket.InputBufferAsString(IndyTextEncoding_ASCII);
    end;

    if wRespData = '' then begin
     Next;
     Continue;
    end;

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

    wIsEvent := wRespData.Contains('Esp:Event') or wRespData.Contains('Esp:Callback');

    if (wIsEvent = True) then begin

     if (xJumpOut = False) then begin
      Result := wRespData;
      Exit;
     end;

     wDidContainEvent := True;

     if not (wRespData.Contains('Esp:Callback')) then begin
      Continue;
     end;

     wWasCashbackEvent := true;
     // Adding Cashback Callback Response Message
     wCBReq := '<?xml version="1.0" encoding="UTF-8"?>';
     wCBReq := wCBReq + '<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">';
     wCBReq := wCBReq + '<Esp:Callback TerminalId="TR000002" EventId="DATA_REQUIRED" ResponseData="{cashbackAmount}" />';
     wCBReq := wCBReq + '</Esp:Interface>';
     // Sending the cashback message with cashback amount to the callback
     ShowMessage(wRespData);
     SendXML(wCBReq, 0, True);
     // Encode the cashback data string into a byte array.
     wCashMsg := IndyTextEncoding_ASCII.GetBytes(wCBReq);
     CopyTIdBytes(wCashMsg, 0, wSendBytes, wHeaderOffset, Length(wCashMsg));
     // Send the data through the socket.
     SendToSocket(wSendBytes);
    end;
    // Return the response
    Result := wRespData;
   end;

  except
   on E:Exception do begin
    TCPClient.Socket.WriteBufferCancel;
    LogMessage(lmtError, ldIn, E.Message, memRequest);
    Exit;
   end;
  end;

 except
  on E: Exception do begin
   TCPClient.Socket.WriteBufferCancel;
   LogMessage(lmtError, ldIn, E.Message, memRequest);
   Exit;
  end;
 end;

end;

相关问题