Delphi Indy equivalent to Python's Socket.recv? - delphi

I've this code snippet in Python:
s = socket.create_connection(('192.168.0.111', 123), timeout=2.0)
s.sendall('REQUEST,status,interface'); result = s.recv(1024)
How I can do the "s.recv(1024)" in Delphi using TIdTCPClient from Indy components? Server returns a string without any terminator, so ReadLn doesn't work.

In Python, recv(1024) simply reads whatever is available on the socket, up to 1024 bytes max. It is possible to do the same thing with TIdTCPClient (see below), but it is not the best way to handle socket programming in general. You really need to know how the server is actually terminating the data. Does it close its end of the connection after sending the data? If not, does it expect you to simply read whatever bytes are available regardless of the actual length? And if so, how does it expect you to handle TCP packet fragmentation (TCP/IP can separate transmitted data into multiple packets, and may receive data as multiple packets even if they were not sent as such)? Does it expect you to keep reading until some timeout occurs, indicating no more data is being sent?
It makes a big difference in how you write code to handle the reading properly. For example:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// wait for a disconnect then return everything that was received
Result := IdTCPClient1.IOHandler.AllData;
finally
IdTCPClient1.Disconnect;
end;
Vs:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// set the max number of bytes to read at one time
IdTCPClient1.IOHandler.RecvBufferSize := 1024;
// read whatever is currently in the socket's receive buffer, up to RecvBufferSize number of bytes
IdTCPClient1.IOHandler.CheckForDataOnSource;
// return whatever was actually read
Result := IdTCPClient1.IOHandler.InputBufferAsString;
finally
IdTCPClient1.Disconnect;
end;
Vs:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// keep reading until 5s of idleness elapses
repeat until not IdTCPClient1.IOHandler.CheckForDataOnSource(5000);
// return whatever was actually read
Result := IdTCPClient1.IOHandler.InputBufferAsString;
finally
IdTCPClient1.Disconnect;
end;

Related

Non overlapped Serial Port hangs at CloseHandle

I wrote a serial port class that I developed and for simplicity I used blocking/synchronous/non-overlapped. I went through all MSDN documentations and it was strait forward for me.
I don't have any problem with Opening, Transmitting or Receiving Bytes from the port. All operations are synchronous and there is no-threading complexity.
function TSerialPort.Open: Boolean;
var
h: THandle;
port_timeouts: TCommTimeouts;
dcb: TDCB;
begin
Result := False;
if Assigned(FHandleStream) then
begin
// already open
Exit(True);
end;
h := CreateFile(PChar('\\?\' + FComPort),
GENERIC_WRITE or GENERIC_READ, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// RaiseLastOSError();
if h <> INVALID_HANDLE_VALUE then
begin
{
REMARKS at https://learn.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_commtimeouts
If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and
sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one
of the following occurs when the ReadFile function is called:
* If there are any bytes in the input buffer, ReadFile returns immediately with the bytes in the buffer.
* If there are no bytes in the input buffer, ReadFile waits until a byte arrives and then returns immediately.
* If no bytes arrive within the time specified by ReadTotalTimeoutConstant, ReadFile times out.
}
FillChar(port_timeouts, Sizeof(port_timeouts), 0);
port_timeouts.ReadIntervalTimeout := MAXDWORD;
port_timeouts.ReadTotalTimeoutMultiplier := MAXDWORD;
port_timeouts.ReadTotalTimeoutConstant := 50; // in ms
port_timeouts.WriteTotalTimeoutConstant := 2000; // in ms
if SetCommTimeOuts(h, port_timeouts) then
begin
FillChar(dcb, Sizeof(dcb), 0);
dcb.DCBlength := sizeof(dcb);
if GetCommState(h, dcb) then
begin
dcb.BaudRate := FBaudRate; // baud rate
dcb.ByteSize := StrToIntDef(FFrameType.Chars[0], 8); // data size
dcb.StopBits := ONESTOPBIT; // 1 stop bit
dcb.Parity := NOPARITY;
case FFrameType.ToUpper.Chars[1] of
'E': dcb.Parity := EVENPARITY;
'O': dcb.Parity := ODDPARITY;
end;
dcb.Flags := dcb_Binary or dcb_Parity or dcb_ErrorChar or
(DTR_CONTROL_ENABLE shl 4) or (RTS_CONTROL_ENABLE shl 12);
dcb.ErrorChar := '?'; // parity error will be replaced with this char
if SetCommState(h, dcb) then
begin
FHandleStream := THandleStream.Create(h);
Result := True;
end;
end;
end;
if not Result then
begin
CloseHandle(h);
end;
end;
end;
function TSerialPort.Transmit(const s: TBytes): Boolean;
var
len: NativeInt;
begin
Result := False;
len := Length(s);
if Assigned(FHandleStream) and (len > 0) then
begin
// total timeout to transmit is 2sec!!
Result := (FHandleStream.Write(s, Length(s)) = len);
end;
end;
function TSerialPort.Receive(var r: Byte): Boolean;
begin
Result := False;
if Assigned(FHandleStream) then
begin
// read timeout is 50ms
Result := (FHandleStream.Read(r, 1) = 1);
end;
end;
My problem starts at closing the port.
After all my communications, when I try to close the serial port, my Application totally hangs at CloseHandle() API. And that happens randomly. Which is meaningless to me since I use synchronous mode, there can not be any pending operations. When I request a close, It must simply close the handle.
I searched the problem on the google and stack-overflow. There are many people who faced the similar problems but most of them are related with .NET serial port driver and their asynchronous mode operations which I don't have.
And also some people forgot to set timeouts properly and they faced blocking issue at ReadFile and WriteFile API that is fully normal. But again this is not my problem, I've set CommTimeouts as it is indicated in MSDN remarks.
function TSerialPort.Close: Boolean;
var
h: THandle;
begin
Result := True;
if Assigned(FHandleStream) then
begin
h := FHandleStream.Handle;
FreeAndNil(FHandleStream);
if h <> INVALID_HANDLE_VALUE then
begin
//PurgeComm(h, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR); // didn't help
//ClearCommError(h, PDWORD(nil)^, nil); // didn't help
//CancelIO(h); // didn't help
Result := CloseHandle(h); <------------ hangs here
end;
end;
end;
Some people on Microsoft forum, suggest calling CloseHandle() in different thread. I have tried that as well. But that time it hangs while trying to free AnonymousThread that I created. Even I left FreeOnTerminate:=true as default, it hangs and I get memory leakage report by Delphi.
Another bothering problem when it hangs, I have to close Delphi IDE fully and reopen. Otherwise I can't compile the code again since exe is still used.
function TSerialPort.Close: Boolean;
var
h: THandle;
t: TThread;
Event: TEvent;
begin
Result := True;
if Assigned(FHandleStream) then
begin
h := FHandleStream.Handle;
FreeAndNil(FHandleStream);
if h <> INVALID_HANDLE_VALUE then
begin
PurgeComm(h, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR);
Event := TEvent.Create(nil, False, False, 'COM PORT CLOSE');
t := TThread.CreateAnonymousThread(
procedure()
begin
CloseHandle(h);
If Assigned(Event) then Event.SetEvent();
end);
t.FreeOnTerminate := False;
t.Start;
Event.WaitFor(1000);
FreeAndNil(t); // <---------- that time it hangs here, why??!!
FreeAndNil(Event);
end;
end;
end;
In my notebook I'm using USB to Serial Port converters from FTDI. Some people said that it is because of FTDI driver. But I'm using all microsoft drivers that is signed by Microsoft Windows Hardware Compatibility Publisher. There is no third party driver in my system. But when I disconnect the USB adapter, CloseHandle API unfreeze itself. Some people reports that, even native Serial Ports that are build in their motherboards have same issue.
So far I couldn't solve the problem. Any help or workaround highly appreciated.
Thanks.
This issue is with the FTDI USB-Serial converter driver. It doesn't handle the hardware flow control properly and on occasion will hang in CloseHandle call.
To get around the issue, implement hardware flow control manually. In C++ (not sure how it would be done in Delphi) set up these DCB structure fields in order to allow manual control of the RTS line:
// Assuming these variables are defined in the header
HANDLE m_hComm; // Comm port handle.
DCB m_dcb; // DCB comm port settings.
// Put these settings in the DCB structure.
m_dcb.fRtsControl = RTS_CONTROL_ENABLE;
m_dcb.fOutxCtsFlow = TRUE;
Then use
EscapeCommFunction(m_hComm, CLRRTS); // Call this before calling WriteFile.
And
EscapeCommFunction(m_hComm, SETRTS); // Call this after Write is complete.
In your case, because its synchronous - you can just wrap every call to WriteFile with these 2 calls. If using asynchronous (like in my case), call the one with SETRTS after you get the completion event from the ovelapped structure in your WriteFile call.
Used to freeze all the time before we implemented this as we were using 12 serial ports, and only way to unlock the port would be restarting the computer.
Now works like a charm with manual control, hasn't frozen once since.
One thing to keep in mind, some USB-Serial devices (or even different versions of FTDI) may invert the RTS line! So if the above doesn't work, try using SETRTS to set the line low and CLRRTS to set it high.
Edit: If you have access to a Windows XP machine, use portmon tool to see what is happening with the RTS line, this way you will know if it is inverted or not or if it is getting the commands at all.

How to cancel a tcpclient stream on Delphi and Indy 10.6

I'm setting up a TCP client/server app, where I send a header record to the server (and possibly a file stream), and it will respond with a response record (and possibly a file stream).
But some times, I get stream errors (both sides), and I can't seem to clear them with IOHandler.InputBuffer.Clear().
Here's some example code:
CLIENT SIDE
var
FS: TFileStream;
FClient.IOHandler.LargeStream := True;
FS := TFileStream.Create(FileName, fmOpenRead);
try
FClient.IOHandler.Write(FS, FS.Size, True); //send the file w/size
finally
FreeAndNil(FS);
end;
SERVER SIDE
var
FS: TFileStream;
//in ServerExecute
FS := TFileStream.Create(FileName, fmCreate Or fmOpenWrite); //create the file
IdContext.Connection.IOHandler.LargeStream := True;
try
if IdContext.Connection.IOHandler.InputBufferIsEmpty then //if no data yet
if not IdContext.Connection.IOHandler.CheckForDataOnSource(30000) then
begin //read timeout
SErr := 'read time out, stream not received!';
Exit;
end;
IdContext.Connection.IOHandler.ReadStream(FS, -1, False); //read stream with size specified at start of stream
if FS.Size <> (expected file size sent in header packet) then
begin
SErr := 'not all stream bytes received!';
Exit;
end;
finally
FreeAndNil(FS); //close the file
end;
Regardless of whether it is the server or client side receiving, when I do IdContext.Connection.IOHandler.ReadStream(FS, -1, False) it will freeze on the stream intermittently (probably because the transfer count is less than the expected bytes). Once I detect a timeout condition, I will clear the input buffer, but from then on it's like the transfers are out of sync; i.e. I send a command packet, then the file packet, but I get an error like it received the file packet first, and it keeps doing that for the following failure retries.

Delphi - Indy 10 #10600 Connection Timeout with POST over 32767 bytes

I'm having a persistent problem using Indy 10 where I am posting a string of XML encoded as a TIdMultiPartFormDataStream, the code that I am using to invoke the POST is given below. A majority of the time, this works no problem, however I am getting a repeated error when running on Windows 7 that throws a #10060 Connection Timeout socket error only when the XML string is longer than 32767 bytes, anything under that size works without a problem.
I have verified that it is not related to the server I am posting to, as there is no problem for messages of any size sent with the same code/application on Windows XP.
IdHTTP2 := TIdHTTP.Create();
UsingHTTPS := (Pos(LowerCase('https://'), Trim(LowerCase(URL)))> 0);
if UsingHTTPS then begin
IdSSLIOHandlerSocketOpenSSL2:=TIdSSLIOHandlerSocketOpenSSL.Create();
with IdSSLIOHandlerSocketOpenSSL2 do
begin
SSLOptions.Method := sslvTLSv1;
SSLOptions.Mode := sslmUnassigned;
SSLOptions.VerifyMode := [];
SSLOptions.VerifyDepth := 0;
Port := 443;
end;
end; //if UsingHTTPS
try
try
with IdHTTP2 do
begin //with some browser style defaults
AllowCookies := False;
ProxyParams.BasicAuthentication := False;
//ProxyParams.ProxyPort := 0;
Request.ContentLength := data.Size;
Request.host := '';
Request.CustomHeaders.Clear;
Request.Accept := '';
Request.ContentEncoding := '';
IOHandler:=IdSSLIOHandlerSocketOpenSSL2;
ReadTimeout := -1;
end;
//http post
IdHTTP2.Post(URL, data, response);
Does anyone have any recommendations to solve this problem? I tried to set the ReadTimeout variable but this had no change. Any pointers to solve this problem would be much appreciated.
TIdSSLIOHandlerSocketOpenSSL sets a hard-coded 30 second read/write timeout on an SSL socket on Windows Vista+ when the ReadTimeout property is <= 0, so chances are the connection is slow enough to take longer than 30 seconds to transmit data. Try setting the ReadTimeout property to a higher value to see if it delays the error. If so, then there really is a transmission issue.
You can use a packet sniffer, such as Wireshark, to make sure data is actually being transmitted back and forth in a timely manner. Also look at the call stack when the exception is raised to see if the timeout is occurring during the SSL handshake, while sending the HTTP request, or while receiving the HTTP response. That will help determine whether it is an issue with OpenSSL, Indy, or the server.

Send Files with TCP from client to server knowing file size Delphi

Hello im trying to send a file Via TCP with delphi using Indy Components with a Client/Server
i manage to send and recieve the files correctly, the problem is before sending the file i would like to send its size aswell to compare it after i get it the server.
Now im sending the files from the Client To Server.
Client:
Ms := TMemoryStream.Create;
Ms.LoadFromFile('cliente.exe');
Ms.Position := 0;
Result := True;
Client.IOHandler.LargeStream := True;
try
Client.IOHandler.Write(ms, 0, True);// (Ms, 0, true);
except
Result := False;
end;
Ms.Free;
Server:
AStream := TFileStream.Create('C:\temp\file.exe', fmCreate + fmShareDenyNone);
try
AContext.Connection.IOHandler.LargeStream := True;
AContext.Connection.IOHandler.ReadStream(AStream, -1,false);
finally
FreeAndNil(AStream);
Memo1.Lines.Add('File received');
end;
So the question would be how could i send the file size with the file?
Your code is already sending the file size. You are setting the AWriteByteCount parameter of Write(TStream) to True, which tells it to send the stream size before sending the stream data. And you are telling ReadStream() to read the stream size before reading the stream data. So Indy is already validating the size for you before ReadStream() exits. You don't need to do it manually at all.

Indy 10 problems with reading and writing streams

I am trying to exchange data between a IdTCPServer and an IdTCPClient using a collection that I convert to a stream before I send it over the network. Unfortunately, no matter how I try, I can't seem to pass the stream between the client and the server. The code always hangs on the IdTCPClient1.IOHandler.ReadStream(myStream, -1, True) line.
The relevant portion of my code is shown below:
Client side
with ClientDataModule do
begin
try
try
intStreamSize := StrToInt(IdTCPClient1.IOHandler.ReadLn); // Read stream size
IdTCPClient1.IOHandler.ReadStream(myStream, -1, True); // Read record stream
finally
ReadCollectionFromStream(TCustomer, myStream);
end;
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server side
try
try
SaveCollectionToStream(ACustomer, MStream);
finally
MStream.Seek(0, soFromBeginning);
IOHandler.WriteLn(IntToStr(MStream.Size)); // Send stream size
IOHandler.Write(MStream, 0); // Send record stream
end;
except
ShowMessage('Unable to save the record to stream');
end;
I would greatly appreciate your assistance with solving this problem.
Thanks,
JDaniel
You are setting the AReadUntilDisconnect parameter of ReadStream() to True, which tells it to keep reading until the connection is closed. You need to set the parameter to False instead. You also need to pass in the stream size in the AByteCount parameter, since you are sending the stream size separately so you have to tell ReadStream() how much to actually read.
Try this:
Client:
with ClientDataModule do
begin
try
intStreamSize := StrToInt(IdTCPClient1.IOHandler.ReadLn);
IdTCPClient1.IOHandler.ReadStream(myStream, intStreamSize, False);
myStream.Position := 0;
ReadCollectionFromStream(TCustomer, myStream);
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server:
try
SaveCollectionToStream(ACustomer, MStream);
MStream.Position := 0;
IOHandler.WriteLn(IntToStr(MStream.Size));
IOHandler.Write(MStream);
except
ShowMessage('Unable to save the record to stream');
end;
If you can change your protocol, then you can let Write() and ReadStream() exchange the stream size internally for you, like this:
Client:
with ClientDataModule do
begin
try
// set to True to receive a 64bit stream size
// set to False to receive a 32bit stream stream
IdTCPClient1.IOHandler.LargeStream := ...;
IdTCPClient1.IOHandler.ReadStream(myStream, -1, True);
myStream.Position := 0;
ReadCollectionFromStream(TCustomer, myStream);
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server:
try
SaveCollectionToStream(ACustomer, MStream);
MStream.Position := 0;
// set to True to send a 64bit stream size
// set to False to send a 32bit stream stream
IOHandler.LargeStream := ...;
IOHandler.Write(MStream, 0, True);
except
ShowMessage('Unable to save the record to stream');
end;

Resources