Data loss in Delphi using TIdTCPServer/TIdTCPClient components - delphi

I'm writing a client-server application using TIdTCPClient/TIdTcpServer indy components in Delphi.
Data transfering usually works fine, but often I read wrong data from server; I get previous requests' answers, not current ones.
During debug, both apps are working locally, so there are no way for data to be lost during transfer.
Timeouts are 1000-3000 msec, this is far enough to avoid sending second requests before receiving answer on the first one.
I use simple data format: first 4 bytes is data packet length, the rest is binary data of that length.
The server-side code is (simplified for sending strings only; I also use binary buffers the same way, but this code is simpler to understand and check):
Var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
lng:=length(s);// s is an AnsiString to be sent
SetLength(ib,lng+4);
Move(lng,ib[0],4);
Move(s[1],ib[4],length(s));
// Send:
AContext.Connection.IOHandler.WriteDirect(ib);
end;
The client-side code for sending request is the same (calling TIdTcpClient.IOHandler.WriteDirect() in last line).
The client-side code for reading server answer is:
Var
ib: TIdBytes;
size,done,lng: LongInt;
begin
Result:=false;
// answer length:
try
SetLength(ib,0);
tcp.IOHandler.ReadBytes(ib,4,false);
Move(ib[0],size,4);
if length(ib)<0 then Exit;// wrong data
except
on E: Exception do;// code skipped
end;
// read answer body:
done:=0;
b.Clear;// b is my buffer, TStream descendant
while done<size do
begin
lng:=Min(size-done,MaxBlockSize);
// read:
SetLength(ib,0);// to be sure
tcp.IOHandler.ReadBytes(ib,lng,false);
if length(ib)=0 then Exit;// error reading
// append my buffer:
b.Wr(ib[0],length(ib));
// progress:
Inc(done,length(ib));
end;
end;
The data exchange order is:
Client sends request to server,
Server reads the request and sends answer back to client,
Client reads the answer.
Wrong data appears on step 3.
Maybe I'm doing something generally wrong?
I've tried to ReadBytes() right before sending request to server to clear the incoming buffer, but that doesn't helps too, as many other things I've tried...
Now I'm just out of ideas :(

Your I/O logic is much more complicated than it needs to be, especially on the client side. You are manually doing things that Indy can do for you automatically.
On the client side, since you are saving the data into a TStream, you can have Indy read the data into the TStream directly:
begin
...
b.Clear;// b is my buffer, TStream descendant
// ReadStream() can read a '<length><bytes>' formatted
// message. When its ASize parameter is -1 and its
// AReadUntilDisconnect parameter is False, it reads
// the first 4 or 8 bytes (depending on the LargeStream
// property) and interprets them as the byte count,
// in network byte order...
tcp.IOHandler.RecvBufferSize := MaxBlockSize;
tcp.IOHandler.LargeStream := False; // read 4-byte length
// read answer:
try
tcp.IOHandler.ReadStream(b, -1, false);
except
on E: Exception do begin
// the socket is now in an indeterminate state.
// You do not know where the reading left off.
// The only sensible thing to do is disconnect
// and reconnect...
tcp.Disconnect;
...
end;
end;
...
end;
On the server side, there are two different ways you can send a message that would be compatible with the above code:
var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
// s is an AnsiString to be sent
lng := Length(s);
SetLength(ib, lng);
Move(PAnsiChar(s)^, PByte(ib)^, lng);
// Send:
AContext.Connection.IOHandler.Write(lng); // send 4-byte length, in network byte order
AContext.Connection.IOHandler.Write(ib); // send bytes
end;
Or:
var
strm: TIdMemoryBufferStream;
begin
// Prepare data to send:
// s is an AnsiString to be sent
strm := TIdMemoryBufferStream.Create(PAnsiChar(s), Length(s));
try
// Send:
// Write(TStream) can send a '<length><bytes>' formatted
// message. When its ASize parameter is 0, it sends the
// entire stream, and when its AWriteByteCount parameter
// is True, it first sends the byte count as 4 or 8 bytes
// (depending on the LargeStream property), in network
// byte order...
AContext.Connection.IOHandler.LargeStream := False; // send 4-byte lengtb
AContext.Connection.IOHandler.Write(strm, 0, True);
finally
strm.Free;
end;
end;
As you can see, this code is sending the same type of messages you were originally sending, what changed is the code that manages the messages. Also, it is forcing the message byte count to be sent in network byte order whereas you were sending it in host byte order instead. Multi-byte integers should always be sent in network byte order when possible, for consistency and multi-platform compatibility.

Related

TIdTCPServer OnExecute fires multiple times

I have an app that receives a TCP socket connection from another app, processes the content that is streamed across the socket, builds a request based on the received data, sends the request using TIdHTTP to an external server, processes the response, and sends an ack back to the tcp client.
procedure TMyClass.TCPExecute(AContext: TIdContext);
var
s: AnsiString;
begin
s := TransferTCPBytesToString(AContext.Connection.IOHandler);
// test string for terminating characters
if Pos(#28#13, s) > 0 then
begin
//********PROBLEM: THIS WILL BE CALLED MULTIPLE TIMES
//********WITH THE SAME COMPLETE DATA STREAM CONTENT!
BuildAndSendExternalRequest(s);
AContext.Connection.IOHandler.InputBuffer.Clear;
// send the response back to tcp client
WriteTCPResponse(AContext.Connection.IOHandler);
end;
end;
function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): AnsiString;
begin
if AnIOHandler.InputBufferIsEmpty then
Exit;
// read message from tcp client connection
AnIOHandler.ReadBytes(FTCPBytes, AnIOHandler.InputBuffer.Size, True);
SetLength(Result, Length(FTCPBytes));
// copy message bytes to string
Move(FTCPBytes[0], Result[1], Length(FTCPBytes));
end;
procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler);
var
bytes: TBytes;
begin
// FTCPBytes contains data from tcp client
// bytes will contain processed response from external server
BuildACK(FTCPBytes,bytes);
// FTCPBytes now contains ACK data
AnIOHandler.WriteDirect(FTCPBytes, Length(FTCPBytes));
end;
The request/response cycle completes successfully; the response sent back to the tcp client requestor is correct and the process executes and completes as expected.
However, because the OnExecute event fires multiple times with the same data, two requests are received, built, sent, acknowledged etc.
In looking at the logs on the tcp client app, the logs only show one request being sent. Not sure what is going on here. But I need to make sure that the request/response cycle is 1-1, not 1-many!
Thanks.
the OnExecute event fires multiple times ...
It is supposed to, as it is a looped event. The loop runs for as long as the connection is alive. The code inside your OnExecute event handler is responsible for deciding what happens on each loop iteration.
The ideal situation is where the event handler receives 1 message from the client, sends 1 response back to the client, and then exits, so that the next loop iteration is ready to handle the next request/response pair.
Which, based on your description, you are trying to do, but the code shown is not handling that very well. Most notably, the code is not accounting for the byte-stream nature of TCP. It does not differentiate one incoming message from another. It just dumps the entire IOHandler.InputBuffer content as-is into an AnsiString (why an AnsiString?), and if that AnsiString happens to contain the delimiter #28#13 then you are processing the entire AnsiString and then throwing it away, losing anything received after the delimiter (ie, part of the next message). And if the AnsiString doesn't contain the delimiter, then you are still throwing away the AnsiString, thus losing all data received so far for the current message before the delimiter arrives.
If all of your messages are delimited by #28#13, then I suggest you use the IOHandler.ReadLn() or IOHandler.WaitFor() method to read each message properly.
Also, you are re-using the same FTCPBytes member for both input and output, which in of itself is wrong, but you are also not providing any concurrent access protection for it if you have multiple client connections present.
With that said, try something more like this instead:
procedure TMyClass.TCPConnect(AContext: TIdContext);
begin
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
end;
procedure TMyClass.TCPExecute(AContext: TIdContext);
var
s: String;
response: TIdBytes:
begin
s := TransferTCPBytesToString(AContext.Connection.IOHandler);
BuildAndSendExternalRequest(s, response);
// send the response back to tcp client
WriteTCPResponse(AContext.Connection.IOHandler, response);
end;
function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
// read message from tcp client connection
Result := AnIOHandler.ReadLn(#28#13);
or:
Result := AnIOHandler.WaitFor(#28#13);
end;
procedure TMyClass.BuildAndSendExternalRequest(const Msg: String; var Response: TIdBytes);
begin
// send Msg to external server as needed...
// store response bytes in Response...
end;
procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler; const Response: TIdBytes);
var
bytes: TIdBytes;
begin
// Response contains processed response from external server
BuildACK(bytes, Response);
// bytes now contains ACK data
AnIOHandler.Write(bytes);
end;
... with the same data
That is not possible given the code you have shown. The call to IOHandler.ReadBytes() in TransferTCPBytesToString() is reading and discarding all bytes from the IOHandler.InputBuffer, so there is no way the same bytes can still be in that buffer when TransferTCPBytesToString() is called again at a later time. So, the only way you could be seeing the same data over and over is if the client is sending the same data over and over to begin with.

Reading unknown amount of bytes from IdTCPServer connection in delphi

I am setting up a client/server program to send and receive unknown amount of byte in my application. How to receive unknown buffer size in my program?
I pass my data to TIdBytes variable and sending it with this line:
IdTCPClient.IOHandler.Write(TIdBytes_Var);
The first byte of TIdBytes_Var determine packet size and varies due to other condition. I take care of the size of the package to not exceed 1024 byte.
On the receiver side (IdTCPServerExecute) I add this line to read all bytes:
var
byte_buffer: TIdBytes;
begin
AContext.Connection.IOHandler.ReadBytes(byte_buffer,-1,false);
end;
On debugging mode, after I send first packet from client to the server, an exception is raised:
raised exception class EConvertError with message ''' is not a valid floating point value
And on running mode only loss my connection.
Where is the problem, and how can I receive all bytes from Indy's input buffer?
Here is my minimal code:
In the client making packet and finally set event that is passed to other thread
//create packet. Buffer_to_send is a TIdBytes variable on unit of network thread (U_network_thread).
SetLength(U_network_thread.Buffer_to_send,(6 + (tmp_ints * 12)));//set buffer lenght dynamically, tmp_ints is my record counter(shortint)
tmp_int:= 2;//tmp_int is the integer variable
U_network_thread.Buffer_to_send[0]:= $1;//header sign
U_network_thread.Buffer_to_send[1]:= tmp_ints;//tmp_ints is my record counter as short integer
while tmp_ints > 0 do
begin
Read(My_File,tmp_rec);//read record from bin file
Move(tmp_rec,U_network_thread.Buffer_to_send[tmp_int],12);//12 is record lenght
tmp_int:= tmp_int + 12; //add pointer
dec(tmp_ints);
end;
tmp_int2:= zone_i;//integer info
Move(tmp_int2,U_network_thread.Buffer_to_send[tmp_int],4); //add info to the packet
Frq_Evt.SetEvent; //set event to triger sending on other thread
On the net_thread, after connecting to the server and receive ack sign by ReadLn command, I wait for event
procedure TNetThread.Execute;
.//create IdTCPClient1 and connect to server
.//receive ack from server by IdTCPClient1.IOHandler.ReadLn command
.//wait for event
while(true) do
if (FEvent.WaitFor(200) = wrSignaled) then//Buffer_to_send fill on main thread
begin
//send buff
if sending_buf(Buffer_to_send) then
begin
//sending ok
end
else
begin
//sending error
end;
end;
end;
end;
function TNetThread.sending_buf(T_Buffer: TIdBytes): boolean;
begin
try
IdTCPClient1.IOHandler.Write(T_Buffer)
result := true;
except
result := false;
end;
end;
On the server side, after connecting, sending ack sign by WriteLn command, and on the IdTCPServerExecute I try to do
procedure Tmain_form.IdTCPServerExecute(AContext: TIdContext);
var
byte_buffer: TIdBytes;
begin
AContext.Connection.IOHandler.ReadBytes(byte_buffer,-1,false);
//do something else
end;
All work fine and connection passed and server send ack sign to client. Client receive it and wait for event. After event occurs, client making up packet and pass it to net_thread. Packet is good and sending is too. But on the server side raise the problem.
the first byte of TIdBytes_Var determine packet size and varies due to other condition.
So, then simply read 1 byte first, then read how many additional bytes it says, eg :
AContext.Connection.IOHandler.ReadBytes(byte_buffer, AContext.Connection.IOHandler.ReadByte, false);
I take care of the size of the package to not exceed 1024 byte.
A single byte can not exceed 255, so your max packet size would be 256, including the size byte.
on debugging mode after i send first packet from client to the server rise a problem and receive:
"raised exception class EConvertError with message ''' is not a valid floating point value"
There is no way the code you have shown can raise that exception.
Update
Here is my minimal code
Seeing your code now, I can see that despite your earlier claim, the first byte of your packet is NOT a packet size, it is always $01. In fact, there is no packet size stored in the packet at all. The 2nd byte contains the number of 12-byte records stored in the packet, and the packet has 6 fixed bytes, so the max packet size is actually 3066 bytes.
Try something more like this:
passed to other thread
// create packet. Buffer_to_send is a TIdBytes variable on unit of network thread (U_network_thread).
SetLength(U_network_thread.Buffer_to_send, (6 + (tmp_ints * 12))); // set buffer length dynamically, tmp_ints is my record counter(shortint)
tmp_int := 2; // tmp_int is the integer variable
U_network_thread.Buffer_to_send[0] := $1; //header sign
U_network_thread.Buffer_to_send[1] := tmp_ints; // tmp_ints is my record counter as short integer
while tmp_ints > 0 do begin
Read(My_File, tmp_rec); // read record from bin file
Move(tmp_rec, U_network_thread.Buffer_to_send[tmp_int], 12); // 12 is record length
Inc(tmp_int, 12); // add pointer
Dec(tmp_ints);
end;
tmp_int2 := GStack.HostToNetwork(UInt32(zone_i)); // integer info, in network byte order
Move(tmp_int2, U_network_thread.Buffer_to_send[tmp_int], 4); // add info to the packet
Frq_Evt.SetEvent; // set event to trigger sending on other thread
procedure Tmain_form.IdTCPServerExecute(AContext: TIdContext);
var
byte_buffer: TIdBytes;
Header: Byte;
Count: Word;
Zone: Integer;
begin
Header := AContext.Connection.IOHandler.ReadByte; // should always be $01
Count := AContext.Connection.IOHandler.ReadByte; // record counter
AContext.Connection.IOHandler.ReadBytes(byte_buffer, Count * 12); // records
Zone := AContext.Connection.IOHandler.ReadInt32; // zone
// process as needed...
end;

Delphi — TClientSocket ReceiveText MaxLen?

I've been trying to set the length of the amount of characters you recover from the ReceiveText TClientSocket function and nothing seems to be working. E.g., Receiving the first leftmost character(s) from the recovered data or otherwise data stream. Is there a way to accomplish this in Delphi using this specific object?
Help would be much appreciated. Thanks in advance.
ReceiveText doesn't have any means to control the maximum length of the received text.
The easiest way in ClientType := ctBlocking mode is to use a TWinSocketStream as the documentation states:
http://docwiki.embarcadero.com/VCL/XE2/en/ScktComp.TClientSocket.ClientType
When ClientType is ctBlocking, use a TWinSocketStream object for reading and writing. TWinSocketStream prevents the application from hanging indefinitely if a problem occurs while reading or writing. It also can wait for the socket connection to indicate its readiness for reading.
Example code:
var
Stream : TWinSocketStream;
Buffer : TBytes;
S : string;
begin
SetLength(Buffer, 100); // 100 bytes buffer size
Stream := TWinSocketStream.Create(Socket, 5000); // 5 seconds or 5000 milliseconds
try
Stream.ReadBuffer(Buffer[0], Length(Buffer)); // raises an Exception if it couldn't read the number of bytes requested
S := TEncoding.Default.GetString(Buffer); // Works in Delphi 2009+
finally
Stream.Free;
end;
end;
here is a little tipp for sending and receiving text
first you must send the length of yout text too
Socket.SendText(IntToStr(Length(text)) + seperator + text);
then you can check at your server socket on receiving data streams, if your incoming text is complete
procedure TMyServer.OnClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin
if (xRecLength = 0) then begin
if Length(Socket.ReceiveText) <= 0 then EXIT;
xRecLength:= StrToIntDef(GetFirstFromSplitted(Socket.ReceiveText, seperator), -1);
if xRecLength = -1 then EXIT;
end;
xActLength:= xActLength + Length(Socket.ReceiveText);
xRecPuffer:= xRecPuffer + Socket.ReceiveText;
isComplete:= xActLength = xRecLength;
if isComplete then begin
// complete text received
end;
end;
hope that helps you...
I'm not at home with Delphi, but a quick Google search turned up this page that indicates that ReceiveText does not accept any parameters, but instead returns a string of as much as it can read.
What you might need is might be ReceiveBuf instead.

problems sending picture from client to server

i am trying to send a picture from 'C:\picture.bmp' to 'c:\temp\picture.bmp' using server and client socket
clients onconnect event handler is as follow:
procedure TForm2.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
var
fs : tfilestream;
begin
fs := TFileStream.create('C:\picture.bmp', fmOpenRead);//picture allready exists
socket.SendStream(fs);
fs.free;
end;
and servers onclientread as :
procedure TForm2.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
fmm : tfilestream;
iLen: Integer;
Bfr: Pointer;
begin
iLen := Socket.ReceiveLength;
GetMem(Bfr, iLen);
fmm := TFileStream.Create('c:\temp\picture.bmp', fmCreate or
fmShareDenyWrite);
try
Socket.ReceiveBuf(Bfr^, iLen);
fmm.Write(Bfr^, iLen);
finally
FreeMem(Bfr);
fmm.Free;
end;
end;
picture is recieved/created but is either corrupt on was never recieved i.e created because of tfilestream.create method?
please help!what am i doing wrong?
Despite its name, SendStream() is NOT guaranteed to send the entire stream (especially if you are using a non-blocking socket). Its return value returns how many bytes are actually sent. If less than the full size of the stream are sent in one call, you have to call SendStream() again, potentially many times, to finish sending the entire stream (the same problems exists with SendText() as well).
On the other side, ReceiveLength() only reports how many bytes are available on the socket AT THAT MOMENT. That is likely to be less than the full stream being sent (likewise, ReceiveText() may not receive a full sent string either because it uses ReceiveLength() internally).
The best way to send a stream (or any arbitrary data in general) is to send the data's size first, then send the actual data afterwards. Keep calling SendBuf/Stream/Text() until that size is reached (if -1 is returned by a non-blocking socket without raising an exception, you have to wait for the socket's OnWrite event to trigger before the socket can accept more data again). On the receiving end, read the size first, then keep reading until the specified size is reached. You may have to read in multiple triggering of the OnRead event before you get all of the data.
Go to http://www.deja.com and http://forums.embarcadero.com to search the Borland/CodeGear/Embarcadero newsgroup/forum archives. I have posted example code many times before.
I don't know what's wrong, but I'd try troubleshooting a simpler problem. i.e. can you even transfer somethign simple? See if you can transfer c:\hello.txt containing just "Hello" and have it arrive in the right order. It should be easier to examine the stream and resulting file, to see if/where things are getting garbled. If you don't receive "Hello" on the server, then you know it's got nothing to do with the size or complexity of the data.

Why do I get an AccessViolation in Indy Sockets 9 IdTcpServer ServerExecute?

First Question:
Is the following routine a correct implementation of an Indy 9 IdTcpServer.OnExecute routine?
procedure TMyConnServer.ServerExecute(AContext: TIdPeerThread);
var
buffSize: integer;
str: string;
begin
AContext.Connection.ReadFromStack(True, 600, False);
buffSize := AContext.Connection.InputBuffer.Size;
if (buffSize > 0) then
{ Extract input buffer as string }
str := AContext.Connection.ReadString(buffSize);
{ Notify connection object of received data }
if (AContext.Data <> nil) then
begin
TConnectionHandler(AContext.Data).Read(str);
end;
end;
end;
Second (actually more important) Question:
Now there is occasionally an access violation (read from address 000000). Obviously in the line:
AContext.Connection.ReadFromStack(True, 600, False);
but checking if AContext / Connection / InputBuffer / IOHandler = nil BEFORE is false.
AFTER the call (and after the exception was raised) the IOHandler is nil.
We are using RAD Studio / Delphi 2007.
The only way the IOHandler can become nil like you describe is if another thread in your app called Disconnect() on the connection while your worker thread was still running.
Well, the simplest onExecute handler I have is like this. (excuse C++ instead of Delphi, but you'll get the idea.
void __fastcall MyPanel::DoTCPExecute(TIdPeerThread * AThread)
{
AnsiString text =AThread->Connection->ReadLn();
// now do something with text
}
The only obvious issue I can see is that you're trying to use the "timing" of data to determine when you have a complete string. This is a real no-no with TCP. You might just have the first byte of a string, or you might have several strings all sent at once. With TCP there's no guarantee that each "send" ends up as single "receive" at the other end.
You need to "delimit" your string in some other way. Readln uses the newline character as a terminator - another approach is to prefix each chunk of data with a length field. You read the length, then read the remaining data.
The code works like that, but I don't think it's a clean option:
if (AContext.Connection.Connected) then
begin
try
AContext.Connection.ReadFromStack(false, 1, false);
except on E: EAccessViolation do
// ignore
end;
end;
buffSize := AContext.Connection.InputBuffer.Size;

Resources