Vždycky jsem UDP psal pomocí Synapse, ale chtěl jsem zkusit něco nového a chtěl jsem to pro Android za pomoci inhouse řešení - i když je Synapse pro Android je dostupná.
Server byl existující synapse UDP server (tady je sample public:howto:udpserver) pro windows, který na zaslaný řetezec <ident> vrátil název počítače, takže jsem během chvilky dostal seznam dostupných stanic i s IP adresou pro připojení z Androidího klienta.
Upozorním na takové dva body, které jsou oříšky v implementaci System.Net.Socket a UDP TSocket, což je platformově nezávislá implementace BSD socketů (UDP, TCP …). Moc jsem o ní ještě nepsal, tak to dnes napravíme.
Celý kod je obsluha tlačítka, které slouží jako Start - Stop. Tlačítko spouští anonymní vlákno kde je jádro kódu. S anonymními vlákny ale pozor na jeden problém - Delphi je sami neukončí při konci programu, protože o nich prostě nemají přehled. Je to celkem známá věc (netýká se to jen anonymních vláken, alesnad vláken obecně) - je nutno před ukončením aplikace nastavit něco na co vlákna reagují a ukončí se, nebo je jinak ukončit a nebo být připraven, že vlákno skončí později, když už nebude např. hlavní vlákno (ba i hůře). Jsou to blbé chyby typu AV, osobně jsem na to narazil při špatném ukončování vlákna se synapsí, kdy anonymní vlákno čekalo na timeout a mezitím se zbytek ukončil. Zajímé obecné řešení publikovala známá autorka knih o Delphi Dalija Prasnikar - Anonymous Threads: Use or Lose?, ale to jen tak mimochodem.
Kód není složitý, takže jen ty dva body.
První trik je oSocket.OpenBroadcast(44009), jedná se o workaround, protože správné řešení by bylo s oSocket.Connect, které funguje, ale zároveň udělá Bind, což je ale problém, když máte server a client pro test na jednom windows počítači. Řešení s OpenBroadcast je jakoby Connect, ale bez interního Bind.
Druhý trik je oSocket.ReceiveFrom s -1, což získá počet čekajících dat, takže se na to dá reagovat, což jsem získal čtením zdrojáků.
A bonus: Copy(buf, 2) dopočítá délku řetezce, tj. klasický třetí parametr.
U TSocket v constructoru specifikuje typ TSocketType.UDP nebo TSocketType.TCP …, případně další varianty s cílovou adresou, endpointem.
Toto je takový základ, hlavně metod okolo ReceiveFrom a Receive je mraky.
Pozor: veškeré updaty UI nutno synchronizovat v případě volání z vlákna
FMX…
…
FScan: Boolean;
lstModules: TListBox;
…
uses
System.Net.Socket, System.IOUtils;
procedure TfrmUDPScan.btnScanClick(Sender: TObject);
begin
FScan := not FScan;
if FScan then
btnScan.Text := 'Stop'
else
btnScan.Text := 'Scan';
if not FScan then
Exit;
lstModules.Clear;
TThread.CreateAnonymousThread(
procedure
var
oSocket: TSocket;
nep: TNetEndpoint;
by: TBytes;
buf: ansistring;
i, iCount: Integer;
b: Byte;
begin
try
oSocket := TSocket.Create(TSocketType.UDP);
try
// ep := TNetEndpoint.Create(TIPAddress.Create(255, 255, 255, 255), 22009);
oSocket.ReceiveTimeout := 1000;
oSocket.SendTimeout := 1000;
// oSocket.Connect('', '255.255.255.255', '', 44009 );
nep := default(TNetEndpoint);
oSocket.OpenBroadcast(44009); // workaround for Connect x Bind
by := TEncoding.ANSI.GetBytes('<ident>');
oSocket.SendTo(by);
repeat
iCount := oSocket.ReceiveFrom(b, -1, nep); // !!!!!!! -1 -> get count of waiting data
if iCount > 0 then
begin
setlength(buf, iCount);
i := oSocket.ReceiveFrom(buf[1], iCount, nep); // get data and sender info
if i > -1 then
begin
TThread.Synchronize(nil,
procedure
begin
lstModules.Items.Add(nep.Address.Address+':'+Copy(buf, 2));
end
);
end;
end;
sleep(10);
until not FScan;
finally
oSocket.Free;
// mLogInfo('broadcast end');
end;
except
on E: Exception do
begin
TThread.Synchronize(nil,
procedure
begin
ShowMessage('Exception:'+E.Message);
end);
end;
end;
end).Start;
end;