Indy 10 Broadcast and AData - delphi

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.

Related

What could be causing IdTCPServer to fail before reading all the data OnExecute event?

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;

Indy UDP sending and responding simple strings

I am using Delphi 10.0 Seattle.
I'd like to send requests to a UDP server and then read the server response, which is a simple string:
Client side:send('12345')
server side(onread event or whatever):if received string = ('12345') then
send ('jhon|zack|randy')
else disconnect;
The length of the response string is variable.
The server is running on a well opened network with open connection (dedicated vps).
The client is not the same, it is behind routers and secure networks (not forwarded).
So far, I can only send the request from the client:
(uc=idUDPclient)
procedure TForm1.Button1Click(Sender: TObject);
var
s:string;
begin
if uc.Connected =False then
Uc.Connect;
uc.Send('12345');
uc.ReceiveTimeout := 2000;
s:=uc.ReceiveString() ;
ShowMessage(s);
uc.Disconnect
end;
Server side (us=idUDPserver)
procedure TForm1.usUDPRead(AThread:TIdUDPListenerThread;const AData: TIdBytes;ABinding: TIdSocketHandle);
begin
ShowMessage(us.ReceiveString());
if us.ReceiveString() = '12345' then
begin
ShowMessage(us.ReceiveString());
//respond with a string to the client immediately (behind a routers) how ?
end;
I don't know if TCP is better, and how to use it.
Android will be involved.
You are not using the TIdUDPServer.OnUDPRead event correctly. You need to get rid of the calls to ReceiveString(), they do not belong in there. Use the AData parameter instead, it contains the raw bytes of the client's request. TIdUDPServer has already read the client's data before firing the event handler.
If you need the bytes in a string, you can use Indy's BytesToString() function, or IIdTextEncoding.GetString() method.
To send a response back to the client, use the ABindingparameter.
Try this:
procedure TForm1.usUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
s: string;
begin
s := BytesToString(AData);
//ShowMessage(s);
if s = '12345' then begin
ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort, 'jhon|zack|randy', ABinding.IPVersion);
end;
end;

Some issues regarding TIdUDPClient and TIdUDPserver

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.

Data missing while sending data from ClientSocket (Mobile) to ServerSocket (Server)

I'm using FireMonkey in Delphi 10.1 Berlin for developing an Android mobile client application, and I'm using VCL in Delphi 10.1 Berlin for developing a Windows server application.
In the mobile application, I am using TIdTCPClient for sending the following record:
PSampleReq = ^TSampleReq ;
TSampleReq = packed record
Value1: array [0..10] of Char;
Value2: array [0..59] of Char;
Value3: array [0..40] of Char;
Value4: Int64;
Value5: array [0..9] of Char;
Value6: array [0..9] of Char;
Value7: Integer;
end;
I have filled the packet with data and am sending the packet using the following code:
FIdTCPClient.IOHandler.Write(RawToBytes(TSampleReq,SizeOf(TSampleReq)));
While reading the data in the Server application, I am not able to read the Value5, Value6and Value7 fields. Below is the code that is reading the data:
Move(tyTIDBytes[0], SampleReq, SizeOf(TSampleReq));
For receiving the data which is send from the client socket, I have used the TIDTcpServer and handled the below code in Execute method:
TServerRecord = packed record
PointerMessage : TIndyBytes;
ClientSocket : TIdTCPConnection;
end;
Var
ReceivedIDBytes: TServerRecord;
begin
if not AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.InputBuffer.ExtractToBytes(ReceivedIDBytes.PointerMessage.tyTIDBytes) ;
ReceivedIDBytes.ClientSocket := AContext.Connection;
MessageProcessorThread.ProcessMessageQueue.Enqueue(ReceivedIDBytes);
end;
After this I'm processing the data from Queue and the processing method I have mentioned below:
var
InputRec: TServerRecord;
begin
InputRec := DBWorkerThread.DBWorkerQueue.Dequeue;
MessageHeaderPtr := #InputRec.PointerMessage.tyTIDBytes[0];
iHMMessageCode := StrToIntDef( Trim(MessageHeaderPtr^.MessageCode), UNKNOWN_MESSAGE_CODE);
case iHMMessageCode of
1001:
begin
Move(InputRec.PointerMessage.tyTIDBytes[0], SampleReq, SizeOf(TSampleReq));
end;
end;
And in this I'm not able to read the Value5, Value6 and Value7 fields.
With the below Link, I have found some optimized technique and how I can handle the packets properly without any packet missing. Please help me out to resolve this issue.
Sending the right record size over socket
Your use of ExtractToBytes() is completely wrong. That method returns whatever arbitrary bytes are stored in the InputBuffer at that particular moment, which may be less than, or more than, what you are actually expecting.
If your client is sending a fixed-sized record each time, you should be reading exactly that many bytes, no more, no less:
var
ReceivedIDBytes: TServerRecord;
begin
AContext.Connection.IOHandler.ReadBytes(ReceivedIDBytes.PointerMessage.tyTIDBytes, SizeOf(TSampleReq)); // <-- HERE!!!
ReceivedIDBytes.ClientSocket := AContext.Connection;
MessageProcessorThread.ProcessMessageQueue.Enqueue(ReceivedIDBytes);
end;
However, if the size of the record depends on the message code, then your client should send the number of bytes in a record before sending the actual record bytes:
var
tyTIDBytes: TIdBytes;
begin
tyTIDBytes := RawToBytes(TSampleReq, SizeOf(TSampleReq));
FIdTCPClient.IOHandler.Write(Int32(Length(tyTIDBytes)));
FIdTCPClient.IOHandler.Write(tyTIDBytes);
end;
And then the server can read the byte count before reading the bytes:
var
ReceivedIDBytes: TServerRecord;
begin
AContext.Connection.IOHandler.ReadBytes(ReceivedIDBytes.PointerMessage.tyTIDBytes, AContext.Connection.IOHandler.ReadInt32); // <-- HERE!!!
ReceivedIDBytes.ClientSocket := AContext.Connection;
MessageProcessorThread.ProcessMessageQueue.Enqueue(ReceivedIDBytes);
end;

Send command to all connected clients

I have a TIdHttpServer i must keep the connection open in order to send some commands back to the clients. I want to iterate when i press a button and send a command to all connected clients.
How can i do this ?
You can use the Contexts property to get the clients and then using the IOHandler of each client you can send a message.
Var
Clients : TList;
i : integer;
begin
if not Assigned(IdTCPServer1.Contexts) then exit;
Clients:=IdTCPServer1.Contexts.LockList;
try
for i := 0 to Clients.Count-1 do
try
TIdContext(Clients[i]).Connection.IOHandler.Write(LBuffer);//LBuffer is a TBytes with the data to send
except
...
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;

Resources