Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Porting old Delphi2007 application using TServerSocket....
#17
(09-18-2020, 10:00 AM)BosseB Wrote: As you pointed out the old code uses string as the container even though the data is not necessarily textual.
So if I were to change that to use TIdBytes instead, what would the read function in the thread look like?
Now with FRxData declared as string:

Code:
FRxData: string;
...
FRxData := FConn.IOHandler.ReadString(FConn.IOHandler.InputBuffer.Size);

If I instead use TIdBytes is the read function going to look like this?
Code:
FRxData: TIdBytes;
...
FConn.IOHandler.ReadBytes(FRxData, FConn.IOHandler.InputBuffer.Size, false);

Yes, or simpler:

Code:
FConn.IOHandler.ReadBytes(FRxData, -1, false);

Which will read everything that is currently available at that moment.

Though, in an STX/ETX scenario, I would suggest something more like this instead (as there is currently no TIdBytes version of TIdIOHandler.WaitFor() available):

Code:
LPos := 0;
repeat
  LPos := FConn.IOHandler.InputBuffer.IndexOf(STX, LPos);
  if LPos <> -1 then Break;
  LPos := FConn.IOHandler.InputBuffer.Size;
  FConn.IOHandler.ReadFromSource(True, FConn.IOHandler.ReadTimeout, True);
until False;

FConn.IOHandler.InputBuffer.Remove(LPos+1);

LPos := 0;
repeat
  LPos := FConn.IOHandler.InputBuffer.IndexOf(ETX, LPos);
  if LPos <> -1 then Break;
  LPos := FConn.IOHandler.InputBuffer.Size;
  FConn.IOHandler.ReadFromSource(True, FConn.IOHandler.ReadTimeout, True);
until False;

FConn.IOHandler.ReadBytes(FRxData, LPos-1, false);
FConn.IOHandler.InputBuffer.Remove(1);

// use FRxData as needed...

Or, if you need the STX and ETX bytes to be in the FRxData buffer:

Code:
LPos := 0;
repeat
  LPos := FConn.IOHandler.InputBuffer.IndexOf(STX, LPos);
  if LPos <> -1 then Break;
  LPos := FConn.IOHandler.InputBuffer.Size;
  FConn.IOHandler.ReadFromSource(True, FConn.IOHandler.ReadTimeout, True);
until False;

if LPos > 0 then
  FConn.IOHandler.InputBuffer.Remove(LPos);

LPos := 1;
repeat
  LPos := FConn.IOHandler.InputBuffer.IndexOf(ETX, LPos);
  if LPos <> -1 then Break;
  LPos := FConn.IOHandler.InputBuffer.Size;
  FConn.IOHandler.ReadFromSource(True, FConn.IOHandler.ReadTimeout, True);
until False;

FConn.IOHandler.ReadBytes(FRxData, LPos+1, false);

// use FRxData as needed...

(09-18-2020, 10:00 AM)BosseB Wrote: Do I have to set the size of FRxData before calling the read or is that handled inside the read?

ReadBytes() will resize the TIdBytes for you.  If the AAppend parameter is True, the read bytes are appended to the end of the TIdBytes' current allocation.  If the AAppend parameter is False, the TIdBytes is set to just the read bytes by themselves.

(09-18-2020, 10:00 AM)BosseB Wrote: Is the synchronized call to the event handler getting a copy of FRxData or FRxData itself?

FRxData itself, since dynamic arrays are reference counted, same as with strings.

(09-18-2020, 10:00 AM)BosseB Wrote: Does it have to set back the length of the container to zero after processing the data?

If you set the AAppend parameter of ReadByes() to True, then yes.

(09-18-2020, 10:00 AM)BosseB Wrote: This is the section of code I ave problem with there.

Indy clients are not event driven, so there are no such events available, so just get rid of all those handlers altogether.

TIdTCPClient.Connect() is synchronous. It will raise an exception in the calling thread if it fails.

If a read/write IO operation fails, including due to a graceful disconnect by the peer, it will raise an exception in the calling thread.

(09-18-2020, 10:00 AM)BosseB Wrote: The compiler stops at the first line complaining about the missing identifier:
Code:
class_SSRemoteClient.pas(110,82) Error: Identifier not found "TErrorEvent"

As it shuld be, since that enum is specific to the old Socket components.  There is no equivalent in Indy.

(09-18-2020, 10:00 AM)BosseB Wrote: I have looked at the documentation and found that there is an event OnStatus that gets fired for any status change, so maybe that could replace all 4 of the above separate events?

Not all 4, no.  You could use it for OnConnect and OnDisconnect, but there are status events for pending data waiting to be read, or for errors.

(09-18-2020, 10:00 AM)BosseB Wrote: There seems also to be other events available when looking in Lazarus code completion, here are all I found:
...
Which one should I use for example in order to create and terminate the read thread?

OnConnected (or OnStatus(hsConnected)), and OnStatus(hsDisconnecting), respectively.

Though honestly, I wouldn't bother with the events at all.  As I mentioned earlier, I would simply create the thread after Connect() exits without error (which you are already doing), and then signal the thread to terminate before calling Disconnect() (which you are already doing), and wait for the thread to finish terminating after Disconnect() exits (which you are not doing), eg:

Code:
procedure TTcpClientComm.Disconnect;
begin
  if FReadThread <> nil then
    FReadThread.Terminate;
  try
    FConn.Disconnect;
  finally
    if FReadThread <> nil then
    begin
      FReadThread.WaitFor;
      FreeAndNil(FReadThread);
    end;
  end;
end;

(09-18-2020, 10:00 AM)BosseB Wrote: Notice that I know when the socket is commanded to connect but not necessarily when it is disconnected since that can be commanded from the client side.

Then you do know when it would be commanded to disconnect, since that is when it would be calling Disconnect() for itself.

(09-18-2020, 10:00 AM)BosseB Wrote: My current implementation of the event driven data reception class using TIdTcpClient shown below.
Does it look sensible now, especially how the thread is created and destroyed in connect/disconnect?

I have a few notes:

- I would suggest making the Data parameter of TCommRxEvent be const to disable unnecessary reference counting.  Same with the WriteString() and WriteData() methods.

- there is no reason for TReadingThread to have its own FOnRxData member.  Give the thread a pointer to the owning TTcpClientComm instead, and then the thread can use the TTcpClientComm.FOnRxData member directly.

- it is not a good idea to use the TIdTCPConnection.Connected() method at all.  It performs a read operation to determine if the socket is still alive, and so that will risk corrupting your communications if the socket is read from by two separate threads at the same time.  If you must have a GetConnected() method in TTcpClientComm then I suggest giving it its own FConnected boolean member that you set to True after calling TIdTCPClient.Connect(), and then set to False when an IO error is detected and/or when you are disconnecting the TIdTCPClient.  And there is no good reason to call Connected() right before performing a read/write operation.  If the socket has been disconnected/lost, the operation will raise an exception.

- your WriteString() method will not work for Unicode strings (Delphi 2009+, or FPC in UnicodeStrings mode).  You should let Indy convert the Data string to a TIdBytes, eg:

Code:
DataBytes := ToBytes(Data, IndyTextEncoding_UTF8);

Alternatively:

Code:
DataBytes := IndyTextEncoding_UTF8.GetBytes(Data);

- your thread's Execute() method can be simplified to this:

Code:
procedure TReadingThread.Execute;
begin
  while not Terminated do
  begin
    // read bytes from FConn up to InputBuffer.Size bytes ...
    FConn.IOHandler.ReadBytes(FRxData, -1, False);
    // process bytes as needed ...
    if Assigned(FOnRxData) then
      Synchronize(DoOnRxData); 
    end;
  end;
end;

Reply


Messages In This Thread
RE: Porting old Delphi2007 application using TServerSocket.... - by rlebeau - 09-18-2020, 07:29 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)