Senai
Senai

Reputation: 15

How to have TIdTCPServer reply every time it receives a message?

I am learning how to work with HL7 and IdTCPClient and IdTCPServer. The HL7 message is received and I get an acknowledgement reply from the server only for the first message. But after that, messages are received but no acknowledgement reply is sent. It hangs at AContext.Connection.IOHandler.WriteLn. How can you make the IdTCPServer send acknowledgement replies for every message it receives? Your input is highly appreciated. Here is the server side code onExcute:

procedure THL7DM.IdTCPServer1Execute(AContext: TIdContext);
  Function AcknowledgementMessage(HL7_msg: string): string;
  var
    s: TStrings;
    MSA: TMSASegment;
    MSH: TMSHSegment;

  begin
    result := '';
    MSH := TMSHSegment.Create(HL7_msg); {HL7 MSH Segment}
    MSA := TMSASegment.Create(''); {HL7 MSA Segment}
    s := TStringList.Create;
    try

      MSH.Accept_Acknowledgment_Type_15 := 'AA';

      MSA.Acknowledgment_Code_18 := 'AA';
      MSH.Sending_Facility_4 := 'AEdge Lab';
      MSH.Message_Type_9 := 'ACK';
      MSA.Message_Waiting_Number_1827 := DateTimeToStr(now);

      s.Text := MSH.ToString + #13 + #10 + 'MSA' + '|' + MSA.ToString;
      s.Text := #11 + s.Text + #28 + #13;
      result := s.Text;
    finally
      MSA.Free;
      MSH.Free;
      s.Free;
    end;
  end;

var
  MsgStrings: TStrings;
  s: string;
  msg: string;
begin
  MsgStrings := TStringList.Create;
  s := AContext.Connection.IOHandler.ReadLn(IndyTextEncoding_OSDefault());
  try
    MsgStrings.Text := StrEscapedToString(s);
    Form2.Memo3.Text := TRegEx.Replace(MsgStrings.Text, #11 + '|' + #28, '');
    msg := AcknowledgementMessage(Form2.Memo3.Text);
     if TRegEx.IsMatch(msg, #28#13) = True then
==>   AContext.Connection.IOHandler.WriteLn(StrStringToEscaped(msg),
      IndyTextEncoding_OSDefault());
   if TRegEx.IsMatch(MsgStrings.Text, #11) = True then
    SaveMessageToDatabase(MsgStrings);
  finally
    MsgStrings.Free;
  end;
end;

Here is the Client side sending the message:

procedure TForm2.BitBtn1Click(Sender: TObject);
var
  LLine: String;
  I: Integer;
  s: string;

begin

  // wrapping for HL7
  LLine := #11 + Memo1.Text + #28 + #13;

  if Receiving_System_Accepts_Escaped_Strings then

    HL7DM.IdTCPClient1.IOHandler.WriteLn(StrStringToEscaped(LLine),
      IndyTextEncoding_OSDefault())
  else
    HL7DM.IdTCPClient1.IOHandler.WriteLn(LLine, IndyTextEncoding_OSDefault());

  if Assigned(ACKReplyHandler) = False then
  begin
    ACKReplyHandler := TACK_MsgHandlingThread.Create;
    //This will handle incoming HL7 ACK replies
  end;

end;

The TACK_MsgHandlingThread looks like this:

procedure TACK_MsgHandlingThread.Execute;
begin
  HandleACK_Replies;
end;

procedure TACK_MsgHandlingThread.HandleACK_Replies;
var
  s: string;
begin
  s := (HL7DM.IdTCPClient1.IOHandler.ReadLn(IndyTextEncoding_UTF8));
 // ShowMessage(s);
  s := StrEscapedToString(s);
  s := TRegEx.Replace(s, #11, '');
  s := TRegEx.Replace(s, #28#13#10, '');

  Form2.Memo4.Clear;

  Form2.Memo4.Text := (s);
end;

Upvotes: 1

Views: 525

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597971

The only way TIdIOHandler.WriteLn() can block is if the receiver is not reading data that has been sent, causing its inbound buffer to fill up and stop the sender from sending more data. This is because your TACK_MsgHandlingThread.Execute() method is reading only 1 incoming reply and then terminating the thread when Execute() exits, so it stops reading subsequent replies. You need to run the logic of HandleACK_Replies() in a loop for the lifetime of the thread, calling TIdIOHandler.ReadLn() for each reply that is sent until the socket is closed and/or the thread is terminated.

Also, IndyTextEncoding_OSDefault is not portable across machine boundaries. But more importantly, you are using IndyTextEncoding_UTF8 on the client side instead. You need to use the same encoding on both sides or else you risk data loss.

Also, your server is accessing Memo3, and your client is accessing Memo4, without syncing with their respective UI threads at all. That is very dangerous. The VCL and FMX frameworks are not thread-safe (most UI frameworks are not), so you MUST synchronize when accessing UI controls from outside of the UI thread.

Upvotes: 1

Related Questions