I have a code which sends audio stream from server to client using TIdTCPClient and TIdTCPServer, it works fine, but it is not efficient when using a lot of clients so I decided to use TIdUDPClient and TIdUDPServer instead but have got some problems which I don't understand (I use the latest indy snapshot).
Server part of code:
.......
//AudioCallback - is a callback function for a 3rd party lib that prepares audio stream, it is called several times per second
...
var fStream:TFileStream;
...
procedure AudioCallback(buffer: LPSTR; len: UINT); cdecl;
var
IdUDPClient: TIdUDPClient;
IdBytes: TIdBytes;
begin
//len is always 1024
IdBytes := RawToBytes(buffer^, len);//works bad
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := cb mod 256;}//works fine
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := Random(255);}//works bad
{ for cb := 0 to High(IdBytes) do
IdBytes[cb] := cb mod 2;}//works fine
fStream.Write(IdBytes[0], length(IdBytes));//for testing purposes
IdUDPClient := TIdUDPClient.Create;
with IdUDPClient do
try
Host := '127.0.0.1';
Port := 32563;
try
Connect;
if Connected then
SendBuffer(IdBytes);
Disconnect;
except
end;
finally
Free;
end;
end;
Client part is simple:
....
var fStream:TFileStream;
....
procedure TClient.IdUDPReadEvent(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
fStream.Write(AData[0],length(AData));
end;
....
The issues that I don't understand:
I tried to use non-local IdUDPClient but the second call of
methods SendBuffer(IdBytes) or Connect always raises an idException
"Invalid argument", though the IdUDPClient properties stayed the
same
The packets sent from the server side and received on the
client differ. I tested it many times. The first received packet is
always the same. The second and the following others are the same
only if you manually fill the packet in some regular way like
IdBytes[cb] := cb mod 256. If you leave it with original data or fill it with random data - you receive not what you sent.
Related
This code work's fine when I send data across the LAN with an Indy client component, but when I receive data from an external application from the web, it's causing it to fail. Could there be something on the client-side that is causing IdTCPServer to disconnect before all the data is read? An average of 33,000 characters are being sent by the client. Any suggestions?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
strm: TMemoryStream;
RxBuf: TIdBytes;
begin
Memo1.Clear;
strm := TMemoryStream.Create;
try
// read until disconnected
AContext.Connection.IOHandler.ReadStream(strm, -1, true);
strm.Position := 0;
ReadTIdBytesFromStream(strm, RxBuf, strm.Size);
finally
strm.Free;
end;
Memo1.Lines.Add(BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
end;
I also tryed this other code, in this case unlike the first code it only reads part of the data beeing sent. Is there a way to make the IdTCPServer Handler wait until all the data is collected?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
RxBuf: TIdBytes;
begin
RxBuf := nil;
with AContext.Connection.IOHandler do
begin
CheckForDataOnSource(10);
if not InputBufferIsEmpty then
begin
InputBuffer.ExtractToBytes(RxBuf);
end;
end;
AContext.Connection.IOHandler.WriteLn('000');
Memo1.Lines.Add( BytesToString(RxBuf) );
end;
This code you posted as an answer is all wrong.
First off, you can't use BytesToString() on arbitrary byte blocks, that won't handle multi-byte encodings like UTF-8 correctly.
Also, you are not looking for the EOT terminator correctly. There is no guarantee that it will be the last byte of RxBuf after each read, if the client sends multiple XML messages. And even if it were, using Copy(BytesToString(), ...) to extract it into a string will never result in a blank string, like your code is expecting.
If the client sends an EOT terminator at the end of the XML, there is no need for a manual reading loop. Simply call TIdIOHandler.ReadLn() with the EOT terminator, and let it handle the read looping internally until the EOT is reached.
Also, the CoInitialize() and CoUninitialize() calls should be done in the OnConnect and OnDisconnect events, respectively (actually, they would be better called in a TIdThreadWithTask descendant assigned to the TIdSchedulerOfThread.ThreadClass property, but that is a more advanced topic for another time).
Try something more like this:
procedure TFrmMain.IdTCPServer1Connect(AContext: TIdContext);
begin
CoInitialize(nil);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TFrmMain.IdTCPServer1Disconnect(AContext: TIdContext);
begin
CoUninitialize();
end;
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
XML: string;
begin
cdsSurescripts.Close;
XML := AContext.Connection.IOHandler.ReadLn(#4);
Display('CLIENT', XML);
AContext.Connection.IOHandler.WriteLn('000');
end;
Personally, I would take a different approach. I would suggest using an XML parser that supports a push model. Then you can read arbitrary blocks of bytes from the connection and push them into the parser, letting it fire events to you for completed XML elements, until the terminator is reached. This way, you don't have to waste time and memory buffering the entire XML in memory before you can then process it.
For further reference to anyone, I had to create a loop and wait for an EOT chr(4) send by the client in order to collect all the data on the IdTCPServer1Execute. This happens because the data is fragmented by Indy, The code looks something like this:
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
Len: Integer;
Loop: Boolean;
begin
CoInitialize(nil);
cdsSurescripts.Close;
Loop := True;
while Loop = true do
begin
if AContext.Connection.IOHandler.Readable then
begin
AContext.Connection.IOHandler.ReadBytes( RxBuf,-1, True);
Len := Length(BytesToString(RxBuf));
if Copy(BytesToString(RxBuf), Len, 1) = '' then
begin
loop := False;
end;
end;
end;
Display('CLIENT', BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
CoUninitialize();
end;
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.
I'm trying to make a text chat through indy udp component and here is the codes for server and client
udp Client:
procedure TForm1.SendClick(Sender: TObject);
begin
sendtocl.Broadcast(usertype.Text, 12000);
usertype.Clear;
end;
onread Server :
procedure TForm1.UDPReceiverUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
var
AudioDataSize: Integer;
AudioData : Pointer;
begin
try
EnterCriticalSection(Section);
try
AudioDataSize := Length(AData);
if AudioDataSize > 10 then
begin
try
if not Player.Active then
begin
Player.Active := True;
Player.WaitForStart;
end;
except
end;
if BlockAlign > 1 then Dec(AudioDataSize, AudioDataSize mod BlockAlign);
AudioData := AudioBuffer.BeginUpdate(AudioDataSize);
try
BytesToRaw(AData, AudioData^, AudioDataSize);
finally
AudioBuffer.EndUpdate;
end;
end else
begin
Player.Active := False;
Player.WaitForStop;
end;
finally
LeaveCriticalSection(Section);
end;
except
end;
begin
chatboxmsg.Lines.Add(BytesToString(AData));
end;
end;
its working good but i had problem if i use the udp client with other purpose like send buffer "To send audio " the chatboxmsg.line shows flooded data of audio buffer any way to make the server read separated Adata ?
In UDP, every send (Broadcast(), Send(), SendBuffer(), etc) transmits a distinct datagram. The OnUDPRead event is triggered for every datagram that is received. AData contains the data of one distinct datagram at a time.
So, you have two choices:
format your datagrams in such a way (such as putting a header at the front of the data) so that they identify the type of data they carry. That way, your OnUDPRead handler can read the identifier/header and know whether to put the remaining data to ChatBoxMsg or pass it to the sound system.
if you do not want to (or cannot) change your datagram formats, then you will have to send text and audio datagrams to different ports. You can use a single TIdUDPServer object listening on multiple ports at the same time (that is what its Bindings collection is for), in which case the ABinding parameter of the OnUDPServer event will tell you which port AData was received on. Or, just use two separate TIdUDPServer objects, each one listening on a different port, and assign different OnUDPRead handlers to each one.
I have a TIdUDPClient created like this:
myUDPClient := TIdUDPClient.Create(nil);
myUDPClient.ReceiveTimeout := 500;
myUDPClient.Binding.Port := 300;
myUDPClient.Active := True;
The binding IP is not specified because I have 3 ethernet adapters with dynamic addressing (192.168.x.x, 10.10.x.x and 172.16.x.x), so the binding IP is generated by Indy and it is 0.0.0.0.
When I receive a packet I can determine the sender IP, but I cannot determine the local IP on which I received the packet (not 0.0.0.0, but one of the 3 IPs assigned to my computer).
Do you know any method to accomplish this?
Do not assign to Binding.Port directly, assign to myUDPClient.BoundPort instead and let TIdUDPClient assign to Binding.Port internally.
Since you are binding to 0.0.0.0 locally, there is no way to determine the destination IP when using TIdUDPClient to read the packet, since it uses the socket API recvfrom() function, hich does not report that info. The socket API getsockname() function will report 0.0.0.0 because that is what the socket is really bound to.
On Windows XP+, you can use GStack.ReceiveMsg() instead to receive packets. It has a TIdPacketInfo output parameter, which contains has a DestIP field (you have to use Binding.SetSockOpt() to enable ReceiveMsg() to collect that info), eg:
...
myUDPClient.Active := True;
myUDPClient.Binding.SetSockOpt(IPPROTO_IP, IP_PKTINFO, 1);
var
Buffer: TIdBytes;
BufLen: LongWord;
PktInfo: TIdPacketInfo;
begin
SetLength(Buffer, ...);
BufLen := GStack.ReceiveMsg(myUDPClient.Binding.Handle, Buffer, PktInfo);
// use Buffer up to BufLen bytes
// PktInfo.DestIP will be the IP that received the packet
end;
Alternatively, you can switch to TIdUDPServer instead, and create a separate entry in its Bindings collection for each local IP you want to receive packets on:
myUDPServer := TIdUDPServer.Create(nil);
myUDPServer.DefaultPort := 300;
myUDPServer.OnUDPRead := UDPRead;
LocalIPs := TStringList.Create;
try
GStack.AddLocalAddressesToList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.IP := LocalIPs[I];
finally
LocalIPs.Free;
end;
{
Or this, if you are using an up-to-date Indy 10 snapshot:
LocalIPs := TIdStackLocalAddressList.Create;
try
GStack.GetLocalAddressList(LocalIPs);
for I := 0 to LocalIPs.Count-1 do
myUDPServer.Bindings.Add.SetBinding(LocalIPs[I].IPAddress, myUDPServer.DefaultPort, LocalIPs[I].IPVersion);
finally
LocalIPs.Free;
end;
}
myUDPServer.Active := True;
procedure TMyClass.DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
begin
// ABinding.IP is the local IP address that received this packet...
end;
I have a problem with Indy TCP connection. I use Turbo Delphi 2006 with Indy 10.
I want to send multiple TCP packages from idTCPClient to idTCPServer.
It works finely, when I want to send only one package, or I insert a sleep(100) command between two calls of the function. But if I call this function too frequently, it doesn't call the server's onExecute every time.
My code for sending:
procedure SendData(var data: TIdBytes) ;
begin
FormMain.IdTCPClient.Connect ;
FormMain.IdTCPClient.Socket.Write(data);
FormMain.IdTCPClient.Disconnect ;
end ;
I call this function several times (5-10 times in a second), and want to process all of these packages in my server application:
procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
data: TIdBytes ;
begin
AContext.Connection.IOHandler.ReadBytes(data, 4, false)
// processing data
end
Thank you for your answers in advance!
Every time you call Connect(), you are creating a new connection, and TIdTCPServer will start a new thread to handle that connection (unless you enable thread pooling, that is). Is that what you really want? It would be more efficient to have the client leave the connection open for a period of time and reuse the existing connection as much as possible. Disconnect the connection only when you really do not need it anymore, such as when it has been idle for awhile. Establishing a new connection is an expensive operation on both ends, so you should reduce that overhead as much as possible.
On the client side, when you call Write(data), it will send the entire TIdBytes, but you are not sending the length of that TIdBytes to the server so it knowns how many bytes to expect. TIdIOHandler.Write(TIdBytes) does not do that for you, you have to do it manually.
On the server side, you are telling ReadBytes() to read only 4 bytes at a time. After each block of 4 bytes, you are exiting the OnExecute event handler and waiting for it to be called again to read the next block of 4 bytes. Unless the length of the client's source TIdBytes is an even multiple of 4, ReadBytes() will raise an exception (causing the server to disconnect the connection) when it tries to read the client's last block that is less than 4 bytes, so your server code will not receive that block.
Try this instead:
procedure SendData(var data: TIdBytes) ;
begin
FormMain.IdTCPClient.Connect;
try
FormMain.IdTCPClient.IOHandler.Write(Longint(Length(data)));
FormMain.IdTCPClient.IOHandler.Write(data);
finally
FormMain.IdTCPClient.Disconnect;
end;
end;
procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
data: TIdBytes;
begin
with AContext.Connection.IOHandler do
ReadBytes(data, ReadLongint, false);
// process data
end;
With that said, if changing the client code to send the TIdBytes length is not an option for whatever reason, then use this server code instead:
procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
LBytes: Integer;
data: TIdBytes;
begin
// read until disconnected. returns -1 on timeout, 0 on disconnect
repeat until AContext.Connection.IOHandler.ReadFromSource(False, 250, False) = 0;
AContext.Connection.IOHandler.InputBuffer.ExtractToBytes(data);
// process data
end;
Or:
procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
strm: TMemoryStream;
data: TIdBytes;
begin
strm := TMemoryStream.Create;
try
// read until disconnected
AContext.Connection.IOHandler.ReadStream(strm, -1, True);
strm.Position := 0;
ReadTIdBytesFromStream(strm, data, strm.Size);
finally
strm.Free;
end;
// process data
end;
Or:
procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
strm: TMemoryStream;
begin
strm := TMemoryStream.Create;
try
// read until disconnected
AContext.Connection.IOHandler.ReadStream(strm, -1, True);
// process strm.Memory up to strm.Size bytes
finally
strm.Free;
end;
end;