Indy UDP sending and responding simple strings - delphi

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;

Related

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.

Indy 10 Broadcast and AData

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.

UDPSocketClient.Receiveln doesn't Proceed all Incoming Data

I'm using Delphi XE4, with UDPSocketClient I send a Request to the Server.
The Request is successful, but the responce from the Server is always about 200 bytes.
It should be about 1000 bytes.
I have no clue why?! There is no EOL break or something like that.
Is it possible to read in Chunks?
procedure TForm1.SendCommand(const Pass, ACommand: string);
var
Cmd: string;
begin
if UDPSocketClient.Connected then
begin
Cmd := Pass + ' ' + ACommand;
UDPSocketClient.Sendln(AnsiString(Cmd));
Memo1.Lines.Add('');
Memo1.Lines.Add('######################');
Memo1.Lines.Add(ACommand);
Memo1.Lines.Add('######################');
Memo1.Lines.Add('');
end;
end;
procedure TForm1.BtnSendCmdClick(Sender: TObject);
var
Buff: AnsiString;
received: string;
begin
if Assigned(CurrentServer) and (CmdEdit.Text <> '') and
(CmdEdit.Text <> CmdEditPlaceHolder) then
begin
SendCommand(CurrentServer.Password, CmdEdit.Text);
end;
try
received := String(UDPSocketClient.Receiveln(Buff));
Memo1.Lines.Add(received);
finally
CmdEdit.SetFocus;
end;
I assume you're using TUDPSocket.
The parameter for ReceiveLn is the delimiter string to search for. You're passing it Buff which is an empty string, so the behaviour is likely to be undefined.
So, now I have an approach to a solution.
When i send the Command to the Server, there are two possibilitys to receive the Data:
The Incoming UDP Package is less than 200bytes, everything will be correct.
If the Incoming UDP Package reached the 1.3kb, then there are two udp packages in the socket buffer. But i can't get the Packages to my Application.

Delphi (Indy) Clients sends server request, how to wait for response?

I have just managed to get the client (IdTCPClient) to send a message to the server (IdTCPServer) as required. But how do I get the client to wait for a response, or time out appropriately?
Cheers,
Adrian
The client can read the response with the IOHandler.Readxxx methods, most of them allow to set a timeout. The read timeout can also be specified on the IdTCPClient.IOHandler directly.
procedure TForm1.ReadTimerElapsed(Sender: TObject);
var
S: String;
begin
...
// connect
IdTCPClient1.Connect;
// send data
...
// use one of the Read methods to read the response.
// some methods have a timeout parameter,
// and others set a timeout flag
S := IdTCPClient1.IOHandler.ReadLn(...);
if IdTCPClient1.IOHandler.ReadLnTimedOut then
...
else
...
end;
See also: How can I wait for a string from a server with IdTCPClient?
For example:
Client:
procedure TForm1.SendCmdButtonClick(Sender: TObject);
var
Resp: String;
begin
Client.IOHandler.WriteLn('CMD');
Resp := Client.IOHandler.ReadLn;
end;
Server:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Cmd: String;
begin
Cmd := AContext.Connection.IOHandler.ReadLn;
...
AContext.Connection.IOHandler.WriteLn(...);
end;
Alternatively, you can use the TIdTCPConnection.SendCmd() method instead:
Client:
procedure TForm1.SendCmdButtonClick(Sender: TObject);
begin
// any non-200 reply will raise an EIdReplyRFCError exception
Client.SendCmd('CMD', 200);
// Client.LastCmdResult.Text will contain the response text
end;
Server:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Cmd: String;
begin
Cmd := AContext.Connection.IOHandler.ReadLn;
...
if (Command is Successful) then
AContext.Connection.IOHandler.WriteLn('200 ' + ...);
else
AContext.Connection.IOHandler.WriteLn('500 Some Error Text here');
end;
In this latter scenario, if you switch to TIdCmdTCPServer, you can use the TIdCmdTCPServer.CommandHandlers collection to define your commands at design-time and assign per-command OnCommand event handlers to them, instead of using the OnExecute event to read and parse the commands manually, eg:
// OnCommand event handler for 'CMD' TIdCommandHandler object...
procedure TForm1.IdCmdTCPServer1CMDCommand(ASender: TIdCommand);
begin
...
if (Command is Successful) then
ASender.Reply.SetReply(200, ...);
else
ASender.Reply.SetReply(500, 'Some Error Text here');
end;
It's been a while since I've used the Indy components (or Delphi for that matter) but I believe that the TIdTCPClient doesn't behave asynchronously so there's no OnData or similar event that you can set.
You will need to call one of the read methods from the parent class (TIdTCPConnection) such as ReadLn(...). Alternatively you could look at using one of the many Indy components that are TIdTCPClient descendants.
Documentation for the class can be found here.

How can I send a broadcast message in Delphi

I want to send a broadcast UDP message in my LAN, the application is client/server.
I desire to update the user interface, this way any computer send a message to update the others.
Can I use UDPServer indy, how to use ?
Thanks
Create two applications, one represents the sender and the other the receiver.
Sender
Drop a TIdUDPClient and a TButton component on your form. On the OnClick handler of the button write:
procedure TfrmUDPClient.BroadcastClick(Sender: TObject);
begin
UDPClient.Broadcast('Test', 8090);
end;
Receiver
Drop a TIdUDPServer on your form, define the same port (8090) for it and add this to the OnUDPRead handler:
procedure TfrmUDPServer.UDPServerUDPRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle);
var
DataStringStream: TStringStream;
Msg: String;
begin
DataStringStream := TStringStream.Create('');
try
DataStringStream.CopyFrom(AData, AData.Size);
Msg := DataStringStream.DataString;
finally
DataStringStream.Free;
end;
ShowMessage(Msg);
end;
Or, in later versions of Indy:
procedure TfrmUDPServer.UDPServerUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
Msg: String;
begin
try
{if you actually sent a string encoded in utf-8}
Msg := TEncoding.UTF8.GetString(AData);
except
end;
ShowMessage(Msg);
end;
To test, run both applications and click on the button. To test with two or more "listeners" you have to use another machine. That is, you can't run multiple listeners on the same IP.
Create a TIdUDPServer or TIdUDPClient component. Both have Broadcast methods that should do exactly what you need.

Resources