UDP Nachrichten versenden und empfangen
Das Transport Protokol UDP ist der kleine Bruder von TCP. UDP ist nicht verlässlich, die Reihenfolge der Pakete ist beim Empfänger eventuell eine andere als beim Absender und es gibt nur eine Fehler-Erkennung, aber keine Fehler-Korrektur. Doch UDP bietet auch Vorteile: Es ist deutlich schneller als TCP (schneller meint hier dass die Pakete schneller beim Empfänger sind), es muss kein aufwändiger Handshake durchgeführt werden, es werden insgesamt weniger Ressourcen verbraucht.
Ein kurzes Beispiel, wie ein UDP Client aussieht, der einfach die IP-Adresse des aktuellen Webbesuchers an einen Server schickt:
<?php $socket = fsockopen('udp://192.168.1.33:10000'); fputs($socket, $_SERVER['REMOTE_ADDR']);
So einfach kann es sein. Dies speichern wir als client.php auf unserem Webserver und lassen beispielsweise Apache Bench laufen:
ab -k -n 2000 -c 50 http://udp.localhost/client.php This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking udp.localhost (be patient) Completed 200 requests Completed 400 requests Completed 600 requests Completed 800 requests Completed 1000 requests Completed 1200 requests Completed 1400 requests Completed 1600 requests Completed 1800 requests Completed 2000 requests Finished 2000 requests Server Software: Apache/2.2.12 Server Hostname: udp.localhost Server Port: 80 Document Path: /client.php Document Length: 0 bytes Concurrency Level: 50 Time taken for tests: 2.747 seconds Complete requests: 2000 Failed requests: 0 Write errors: 0 Keep-Alive requests: 2000 Total transferred: 540050 bytes HTML transferred: 0 bytes Requests per second: 728.03 [#/sec] (mean) Time per request: 68.679 [ms] (mean) Time per request: 1.374 [ms] (mean, across all concurrent requests) Transfer rate: 191.98 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 1 Processing: 13 68 4.6 68 86 Waiting: 13 68 4.6 68 86 Total: 13 68 4.5 68 86 Percentage of the requests served within a certain time (ms) 50% 68 66% 69 75% 70 80% 71 90% 72 95% 73 98% 75 99% 76 100% 86 (longest request)
Wir können also ungefähr 700 Requests pro Sekunde abfeuern. Aber halt, wohin werden die UDP-Pakete verschickt? Richtig, sie gehen ins Nirwana, es läuft noch kein entsprechender Server, und da UDP sich nicht darum kümmert ob die Pakete ankommen oder nicht (fire and forget) funktioniert es. Würden wir TCP verwenden, wäre unser Skript sehr langsam, es würde versuchen eine Verbindung aufzubauen, und nach X Sekunden würde der Versuch abgebrochen falls kein Server gefunden wird, und es hagelt Fehlermeldungen.
In diesem Fall wäre es uns egal falls der Server (Sammler) nicht läuft, denn es sollen nur IP-Adressen geloggt werden oder beispielsweise soll ein kleines Reporting-Tool einige Statistiken sammeln. Die Daten sind nicht so wichtig als dass die Webseite darunter leiden soll wenn der Reporting-Server mal nicht erreichbar ist.
Wie sieht ein einfacher Server aus?
<?php $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); if (!$socket) die('Unable to create socket'); if (!socket_bind($socket, null, 10000)) die('Unable to bind socket'); while(true) { $data = @socket_read($socket, 15); echo $data . "\n"; // oder in eine Datei schreiben }
10000 ist der Port, auf dem wir die Nachrichten empfangen wollen. null bedeutet hier dass wir auf allen Interfaces lauschen wollen, hier könnte man auch nur bestimmte Adressen (bspw: 192.168.1.33 oder localhost) nehmen. Da es keine Authentifizierung gibt sollte natürlich darauf geachtet werden wo man den Port öffnet. Die 15 bedeutet dass wir maximal 15 Byte erwarten, da eine IPv4-Adresse nicht länger sein kann. Sollen beliebig große Nachrichten empfangen werden muss dies erhöht werden.
Schönes Beispiel.
Aber sollte in der while(true) Schleife nicht noch
ein sleep eingebaut werden, damit der Server nicht ständig auf 100% Last läuft? Oder ist das in PHP anders?
webthief
3 Mrz 11 at 17:26
@webthief: In diesem Fall braucht man das nicht, da der socket_read() Aufruf blockierend ist, sprich PHP bleibt an dieser Stelle stehen bis Daten kommen.
Michael Kliewe
3 Mrz 11 at 18:04
ah, gut zu wissen
danke
webthief
5 Mrz 11 at 15:11
[…] PHP: UDP-Nachrichten versenden und empfangen. […]
Linkhub – Woche 09-2011 | PehBehBeh
7 Mrz 11 at 16:58
Hi,
interessante Möglichkeit. Da der Server aber keine mehreren Child-Prozesse für jede Anfrage öffnet…
Wieviele von den 2000 schnell hintereinander abgegebenen Request hätte der denn tatsächlich auch verarbeitet?
Axel
11 Mrz 11 at 19:21
@Axel: Der Server hat die Nachrichten glaube ich alle verarbeitet, ich habe sie zum Test nur auf der Console ausgegeben und nicht weggeloggt (siehe Script oben).
Besser wäre evtl, wie du schon sagst, ein non-blocking Server ähnlich wie hier, um das ganze etwas besser zu skalieren:
https://www.phpgangsta.de/wie-erstelle-ich-einen-socket-server-in-php
Michael Kliewe
12 Mrz 11 at 11:28
[…] UDP-Library UDP Server und Client mit PHP Gefällt mir:Gefällt mirSei der Erste dem dies gefällt. Dieser Beitrag wurde unter Arduino, […]
UDP | wer bastelt mit?
27 Jun 12 at 03:03
Hi,
wenn ich den Socket geöffnet habe, wie kann man ihn wieder schließen?
Philipp
21 Aug 17 at 12:55
@Philipp: Du meinst wenn du den Socket mit socket_create() erstellt hast?
http://php.net/socket_create
Rechts in der Liste, oder unten in den Kommentaren steht die Antwort: socket_close($socket);
Michael Kliewe
21 Aug 17 at 13:00
aber wenn er permanent lauschen soll und ich ihn erst wieder irgendwann schließen möchte?
https://www.php.de/forum/webentwicklung/php-einsteiger/1511615-php-socket-open-close
Philipp
21 Aug 17 at 13:08
@Philipp: Ein Socket wird spätestens geschlossen wenn das PHP-Script beendet wird. Das passiert automatisch.
Wenn du den Socket „irgendwann“ schliessen möchtest, dann musst du an die Stelle im Code, wann „irgendwann“ ist, socket_close() aufrufen. Wenn also irgendein externes Ereignis auftritt, oder eine bestimmte Nachricht gesendet wurde (z.B. QUIT) oder sowas.
Bist du „horphi“, der Fragensteller?
lstegelitz hat recht, du hast das Prinzip von HTTP noch nicht verstanden. Wenn du einen HTTP-Befehl zum Starten schickst, dann läuft der Socket-Server in einer Endlosschleife, als Apache-PHP-Prozess. Allerdings bricht der Socket-Server nach meistens 30 Sekunden ab und beendet sich, weil PHP via Apache gestartet wurde, und je nach max_execution_time wird dein Socket-Server gekillt.
Lang laufende PHP-Prozesse müssen immer auf der Kommandozeile laufen, da sie ja über Stunden, Tage und Monate laufen sollen. Das geht mittels eines Apache-PHP-Prozesses nicht. Deshalb startet man Server (Daemons) immer auf der CLI Kommandozeile.
Du kannst den so gestarteten Socket-Server dann natürlich mittels kleiner HTTP-Befehle von außen steuern, in dem du einen HTTP-Request via Apache zum Server schickst, und der Apache-PHP-Prozess sagt dem CLI-PHP-Prozess dass er stoppen soll. Zum Beispiel in dem er eine Datei „stop.txt“ in den Ordner legt, und der Socket-Server jede Sekunde einmal prüft ob es die Datei gibt. Oder aber man sendet eine spezielle Nachricht an den Socket-Server („QUIT“), und er beendet sich darauf hin.
Michael Kliewe
21 Aug 17 at 13:25
Hi,
Fragesteller: ja
Danke fürs Feedback.
Also dann eher nach deiner Methode. 😉
https://www.phpgangsta.de/wie-erstelle-ich-einen-socket-server-in-php
Philipp
21 Aug 17 at 13:48