Link to home
Start Free TrialLog in
Avatar of bjones8888
bjones8888Flag for United States of America

asked on

Writing stream data & XML data to TIdTCPClient and TIdIOHandlerStream (Delphi 2006 & Indy10)

I am trying to implement a client connection to a server (TCP).  I know the server is working because there's a VB app that makes an XMLQuery and receives a response.

I need to do the same with my Delphi 2006 Architect & Indy 10 application.  Assume I know VERY little in this area and you will be close to the truth.  

I was told by the folks who have the XML database server that I first have to send the size of the query in network byte order, get an Ack or Nak back, then send the query itself, and then I'll get an Ack or Nak, then the actual XML response.  I'm getting an AV on the first IOHandlerStream.Write.  Obviously, I don't understand what I'm doing.  This must be installed later today. HELP!!  

Here's my code in the FormActivate event.

procedure TForm1.FormActivate(Sender: TObject);
var
  s: string;
  sXML: string;
  iSize: longint;
  iLong: longint;
  pLong, pXML: PChar;
begin
    with Client do
    begin
      Connect;
      try
        sXML := '<?xml version="1.0" encoding="UTF-8"?>' + #13#10 +
          '  <PublicSafetyEnvelope version="1.0">' + #13#10 +
          '    <MessageIdentification/> <From/> <To/> <Creation/>' + #13#10 +
          '    <PublicSafety id="">' + #13#10 +
          '      <Query>' + #13#10 +
          '        <MainNamesTable>' + #13#10 +
          '          <NameNumber search_type="equal_to">9</NameNumber>' + #13#10 +
          '        </MainNamesTable>' + #13#10 +
          '      </Query>' + #13#10 +
          '    </PublicSafety>' + #13#10 +
          '  </PublicSafetyEnvelope>';
        pXML := PChar(sXML);
        iSize := SizeOf(pXML);
        iLong := htonl(iSize);
        pLong := PChar(iLong);
        IOHandlerStream.Write(pLong^);
        IOHandlerStream.Write(pXML^);
        s := IOHandlerStream.ReadLn;
        ShowMessage(s);
      finally
        Disconnect;
      end;
    end;
end;
Avatar of bjones8888
bjones8888
Flag of United States of America image

ASKER

I've made progress, but still don't quite have it.  Now I get a response without errors.  And it even appears to be returning the right NUMBER of characters, but they're all spaces.  If I take what the VB app returns and paste it into notepad & save it, the file is 1518 bytes long.  My app now returns 1479 characters, but they're all spaces or non-displayable characters.  Any help?  Still 500 points to whomever can correct the error of my ways!

procedure TForm1.FormActivate(Sender: TObject);
var
  s: string;
  sXML: string;
  sStrings: TStringList;
  iSize: integer;
  iLong, i: integer;
begin
    with Client do
    begin
      Connect;
      try
        sXML := '';
        sStrings := TStringList.Create;
        sStrings.Add('<?xml version="1.0" encoding="UTF-8"?>');
        sStrings.Add('  <PublicSafetyEnvelope version="1.0">');
        sStrings.Add('    <MessageIdentification/> <From/> <To/> <Creation/>');
        sStrings.Add('    <PublicSafety id="">');
        sStrings.Add('      <Query>');
        sStrings.Add('        <MainNamesTable>');
        sStrings.Add('          <NameNumber search_type=''equal_to''>9</NameNumber>');
        sStrings.Add('        </MainNamesTable>');
        sStrings.Add('      </Query>');
        sStrings.Add('    </PublicSafety>');
        sStrings.Add('  </PublicSafetyEnvelope>');
        for i := 0 to sStrings.count - 1 do
          sXML := sXML + sStrings[i];
        iSize := Length(sXML);
        IOHandlerStack.open;
        IOHandlerStack.Write(iSize, True);       ' theoretically this puts it into network byte order and writes the 4 byte header
        IOHandlerStack.Write(sXML);
        s := IOHandlerStack.AllData;
        IOHandlerStack.Close;
      finally
        Disconnect;
      end;
    end;
    iLong := Length(s);
    ShowMessage('Returned ' + IntToStr(iLong) + ' bytes. Here they are:' + Chr(13) + Chr(10) + s);
end;


For what it's worth, here's the VB app code that was given to me.

'/////  This is the code that runs when the button on the form is clicked.  
    Private Sub submitButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles submitButton.Click
        Dim myQuery As XMLQuery
        myQuery = New XMLQuery

        myQuery.Hostname = hostnameTextBox.Text     ' this holds the tcp/ip address
        myQuery.Port = Long.Parse(portTextBox.Text)   ' this holds the port number

        ' queryRichTextBox contains the same text I'm putting into sXML above
        ' responseRichTextBox receives the result of the query
        responseRichTextBox.Text = myQuery.SubmitQuery(queryRichTextBox.Text)
    End Sub

'//// this is XMLQuery.vb
'--------------------------------------------
        '-- Send the 4 byte header
        Dim LengthBuffer = BitConverter.GetBytes(LengthInNetworkByteOrder)
        ClientSocket.Send(LengthBuffer, 4, SocketFlags.None)

        '-- Send the data
        BytesToSend = New System.Text.ASCIIEncoding().GetBytes(Msg)
        ClientSocket.Send(BytesToSend)

        '-- Read the ACK (\006) or NAK (\025)
        Dim AckOrNak(1) As Byte
        ClientSocket.Receive(AckOrNak, 1, SocketFlags.None)
        If AckOrNak(0) <> 6 And AckOrNak(0) <> 25 Then
            '-- Invalid ACK/NAK received
            MessageBox.Show("Invalid ACK/NAK received: " + AckOrNak(0).ToString())
            Return ""
        ElseIf AckOrNak(0) = 25 Then
            '-- NAK received
            MessageBox.Show("NAK received from XML Query Server")
            Return ""
        End If

        '-- Read the 4 byte response length into LengthBuffer
        Dim Numbytes As Int32
        Numbytes = ClientSocket.Receive(LengthBuffer, 4, SocketFlags.None)

        '-- Convert the Response length into the host's byte order
        Dim ResponseLengthInNetworkByteOrder = BitConverter.ToInt32(LengthBuffer, 0)
        Dim ResponseLength = IPAddress.NetworkToHostOrder(ResponseLengthInNetworkByteOrder)

        '-- Receive the message
        Dim ResponseBuffer(50) As Byte
        Dim BytesSoFar As Int32
        Dim NumBytesToRead As Int32 = Numbytes
        ResponseString = ""

        While NumBytesToRead > 0

            Numbytes = ClientSocket.Receive(ResponseBuffer, NumBytesToRead, SocketFlags.None)
            If Numbytes > 0 Then
                ReDim Preserve ResponseBuffer(Numbytes - 1)
                '-- Convert the byte array into an ASCII string and add it to ResponseString
                ResponseString += System.Text.ASCIIEncoding.ASCII.GetString(ResponseBuffer)
                BytesSoFar += Numbytes
            End If

            If ResponseLength < BytesSoFar + ResponseBuffer.Length Then
                NumBytesToRead = ResponseLength - BytesSoFar
            Else
                NumBytesToRead = ResponseBuffer.Length
            End If

        End While

        '-- Shutdown and Close the socket
        ClientSocket.Shutdown(SocketShutdown.Both)
        ClientSocket.Close()

        Return ResponseString
    End Function
End Class
Here's the solution.  The problem was that the ACK characters in response to the write statements weren't being removed from the stream.  For some reason those characters were causing the ShowMessage statement to display as all spaces, but the correct response was actually in the response stream.

After stripping those off the beginning of the stream, the display was correct.
ASKER CERTIFIED SOLUTION
Avatar of kodiakbear
kodiakbear

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial