Forum: Mikrocontroller und Digitale Elektronik Linux, Python: UDP-Server startet nicht


von Manfred (Gast)


Lesenswert?

Guten Morgen!

Nach dem Einschalten eines Raspberry Zeros wird per 'cron daemon' die 
AtBoot.sh ausgeführt(1). 'webserver.py' startet, UDPServer.py nicht.

Beide habe die gleichen Rechte(2, 3, 4).

Das Aufrufen von der Kommandozeile startet den UDP-Server einwandfrei.
1
pi@raspberryzero:~ $ /home/pi/python/UDPServer.py &

Warum wird der UDP-Server durch das Ausführen der AtBoot.sh nicht 
gestartet?

Manfred
1
---(1)---------------------------------------------------------
2
pi@raspberryzero:~ $ cat AtBoot.sh
3
#!/bin/sh
4
5
cd /home/pi/index
6
python3 /home/pi/index/webserver.py &
7
python3 /home/pi/python/UDPServer.py &
8
---------------------------------------------------------------
9
10
---(2)---------------------------------------------------------
11
pi@raspberryzero:~ $ ls -l
12
total 12
13
-rwxr-xr-x 1 pi pi  106 Nov 14 06:11 AtBoot.sh
14
drwxr-xr-x 6 pi pi 4096 Nov 13 12:08 index
15
drwxr-xr-x 2 pi pi 4096 Nov 14 05:56 python
16
---------------------------------------------------------------
17
18
---(3)---------------------------------------------------------
19
pi@raspberryzero:~ $ ls /home/pi/index/webserver.py -l
20
-rwxr-xr-x 1 pi pi 126 Apr  6  2021 /home/pi/index/webserver.py
21
---------------------------------------------------------------
22
23
---(4)---------------------------------------------------------
24
pi@raspberryzero:~ $ ls /home/pi/python/UDPServer.py -l
25
-rwxr-xr-x 1 pi pi 625 Nov 13 17:11 /home/pi/python/UDPServer.py
26
---------------------------------------------------------------

UDP-Server
1
import socket
2
import time
3
4
UDP_IP   = b"xxx.xxx.xxx.xxx"   ## IP Raspberry
5
UDP_PORT = 8086                 ## Port Raspberry
6
7
print ("UDP Server IP:", UDP_IP)
8
print ("UDP Server Port:", UDP_PORT)
9
10
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
11
sock.settimeout(1)
12
 
13
Message = bytes(str(time.time()), 'utf-8')
14
15
try:    
16
    sock.sendto(Message, (UDP_IP, UDP_PORT))
17
18
    data, addr = sock.recvfrom(1024)
19
    print('received:', data, 'from', addr)
20
except:
21
    pass
22
    
23
sock.close()

Webserver
1
#!/usr/bin/python3
2
import http.server
3
http.server.HTTPServer(("", 8000),http.server.CGIHTTPRequestHandler).serve_forever()

von error message (Gast)


Lesenswert?

Unterschiedliche Aufrufe und CWD?

von Manfred (Gast)


Lesenswert?

Wenn ich die AtBoot.sh von der Kommandozeile aufrufe, werden der 
Webserver und UDP-Server gestartet. Das Verzeichnis spielt dabei keine 
Rolle.

von Thomas W. (Gast)


Lesenswert?

Moin, -

bei /home/pi/python/UDPServer.py fehlt der Befehlsinterpreter, vulgo 
#!/usr/bin/python3

Gruesse

Th.

P.S.: Ist mir oft genug passiert...

von Manfred (Gast)


Lesenswert?

Thomas W. schrieb:

> bei /home/pi/python/UDPServer.py fehlt der Befehlsinterpreter, vulgo
> #!/usr/bin/python3

Da der Name des Interpreters python3 vor jedem Python-Skript in der 
AtBoot.sh steht und das Ausführen von AtBoot.sh von der Kommandozeile 
funktioniert, kann das nicht sein. Habe die Zeile zum Testen trotzdem 
hinzugefügt. Das Ergebnis ist wie erwartet, der UDPServer nicht startet.

Der Haken muss irgendwo anders sein.
1
#!/usr/bin/python3
2
3
## Raspberry UDPServer
4
5
import socket
6
import time
7
8
UDPServerIP   = '192.168.178.29'
9
UDPServerPort = 8086
10
11
ServerACK = b'ACK'
12
13
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
14
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
15
sock.bind((UDPServerIP, UDPServerPort))
16
17
print('Warte ...')
18
19
while True:
20
    ClientData, ClientAddr = sock.recvfrom(1024)
21
    sock.sendto(ServerACK, ClientAddr)
22
    print(ClientData, ':', ClientAddr)
23
24
    fp = open('/home/pi/index/ramdisk/temps.txt', 'a')
25
    fp.write(ClientData.decode('utf-8') + ' ' + time.strftime('%H:%M', time.gmt$
26
    fp.close()

von Thomas W. (Gast)


Lesenswert?

Manfred schrieb:
> Thomas W. schrieb:
>
>> bei /home/pi/python/UDPServer.py fehlt der Befehlsinterpreter, vulgo
>> #!/usr/bin/python3
>
> Da der Name des Interpreters python3 vor jedem Python-Skript in der

Du hast Recht, ich bin noch nicht da...

Gruesse

Th.

von Thomas W. (Gast)


Lesenswert?

Manfred schrieb:
1
    fp = open('/home/pi/index/ramdisk/temps.txt', 'a')

Das Verzeichnis /home/pi/index/ramdisk existiert?

Gruesse

Th., mit ca. 50% geistiger Leistung...

von Εrnst B. (ernst)


Lesenswert?

Du schreibst in deinem UDP-Server auf STDOUT. Kann es sein, dass das 
nicht mehr verfügbar ist, wenn's aus der AtBoot gestartet wird?

versuch mal

>> python3 /home/pi/python/UDPServer.py > /home/pi/udp-server.log

oder

>> screen -dmS UDPsever python3 /home/pi/python/UDPServer.py

im atboot.

von Ein T. (ein_typ)


Lesenswert?

Hallo Manfred,

Manfred schrieb:
> Nach dem Einschalten eines Raspberry Zeros wird per 'cron daemon' die
> AtBoot.sh ausgeführt(1). 'webserver.py' startet, UDPServer.py nicht.

Wir könnten jetzt natürlich anfangen, Deinen Code zu debuggen und zum 
Beispiel erstmal ein anständiges Logging (jede 
Python-Standardinstallation bietet dazu das Modul "logging") in Deinen 
Code einzubauen. Aber um ganz ehrlich zu sein, widerstrebt mir das, denn 
an Deinem Ansatz ist so viel Unelegantes und Krudes... es erscheint mir 
sinnvoller, die Angelegenheit grundsätzlicher anzugehen, so daß sie 
langfristig funktionieren kann.

Langlebige Prozesse mit Crons @reboot-Feature zu starten ist möglich, 
aber maximal unelegant. Eleganter ist es, für Deine beiden Prozesse 
jeweils ein systemd-Unitfile anzulegen, so daß sich systemd um das 
Management Deiner Prozesse kümmern und sie starten und stoppen, sich um 
das Logging kümmern, sie monitoren und gegebenenfalls restarten kann.

Nebenbei scheinst Du zu versuchen, die Dinge möglichst schnell und 
einfach zu lösen, anstatt die Sache richtig zu machen. Ich nehme das an, 
weil Du UDP benutzt, was nur in wenigen begründeten Ausnahmen eine gute 
Idee ist, aber von Einsteigern gerne benutzt wird, weil es ihnen 
zunächst einfacher erscheint. Zudem schreibst Du Dein UDP-Skript direkt 
mit dem Modul socket, anstatt die in der Python-Standardinstallation 
bereits fertig vorhandene Klasse UDPServer aus dem Modul socketserver zu 
verwenden. Offensichtlich hast Du Dich also nicht sonderlich tief in die 
Möglichkeiten der Python-Library eingearbeitet... so sieht das 
jedenfalls für mich aus. Aber sehen wir uns mal Dein UDP-Skript etwas 
genauer an.

Da ist keine Schleife im UDP-"Server", Fehler werden unterdrückt -- 
except ohne explizite Angabe der abzufangenden Exception ist keine gute 
Idee, und für ein except: pass müßte man schon extrem triftige Gründe 
haben. Zudem sendest Du zuerst Daten (sock.sendto()) an den Port Deines 
Skripts, dann versuchst Du Daten zu lesen (sock.recvfrom())... an dieser 
Stelle sind die Daten, die Du gerade an den Port versendet hast, aber 
ohnehin schon längst im Nirvana verschwunden. Dann läuft Dein UDP-Skript 
in den zuvor gesetzten Timeout (sock.settimeout()), aber anstatt diesen 
Fehler (oder idealerweise den kompletten Traceback mit der Funktion 
print_exc() aus dem Standardmodul traceback) auszugeben, verschluckst 
und unterdrückst Du ihn. Darum glaube ich, daß Dein UDP-Skript zwar 
korrekt gestartet wird, sich nach Ablauf des Socket-Timeout aber (wegen 
Deiner Fehlerunterdrückung stillschweigend) gleich wieder völlig korrekt 
beendet.

Machen wir es kurz: das, was Du da bisher gemacht hast, ist leider nicht 
ansatzweise sinnvoll, und selbst dann, wenn Du es irgendwann ans Laufen 
bekommst, wird es Dir langfristig auf die Füße fallen. Vielleicht fangen 
wir die Sache daher mal konstruktiv an, indem Du uns erzählst, was Du da 
eigentlich vorhast -- also: warum Du einen HTTP- und einen UDP-Server 
starten willst, und was sie ganz konkret tun sollen ("soll HTTP-Anfragen 
bedienen" ist keine sinnvoll Erklärung, sorry). Und dann überlegen wir 
gemeinsam eine konstruktive Lösung, was meinst Du?

von Axel S. (a-za-z0-9)


Lesenswert?

Der UDP-Server gibt noch was auf die Konsole aus. Vielleicht liegts 
daran. Es ist generell schlechter Stil, Prozesse im Hintergrund 
auszuführen, ohne ihre Standard-Ausgabe in irgendein File umzuleiten. 
Und sei es /dev/null. Also z.B.
1
$ cat AtBoot.sh
2
...
3
python3 /home/pi/python/UDPServer.py >udp.log 2>&1 &

Noch besser wäre es, wenn man die Distributions-Infrastruktur richtig 
benutzen würde. Also z.B. start-stop-daemon. Und am besten wäre, wenn 
man eine systemd-Unit oder zumindest ein init-Script dafür verwenden 
würde.

Ach ja. Das Thema ist hier unpassend. "PC-Hard- und Software" würde 
besser passen. Am Ende des Tages ist der RasPi ein PC.

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Εrnst B. schrieb:
> Du schreibst in deinem UDP-Server auf STDOUT. Kann es sein, dass das
> nicht mehr verfügbar ist, wenn's aus der AtBoot gestartet wird?

STDOUT sollte noch verfügbar sein, das wird ja beim Start des Skripts im 
Hintergrund vom aufrufenden Prozeß übernommen. Der ist AtBoot.sh, und 
das bekommt STDOUT vom Cron-Daemon -- der dann alles, was auf dieses 
STDOUT geschrieben wird, in eine Mail einpackt, die entweder an "root" 
oder an einen in der Umgebungsvariable MAIL konfigurierten User sendet.

von Ein T. (ein_typ)


Lesenswert?

Thomas W. schrieb:
> bei /home/pi/python/UDPServer.py fehlt der Befehlsinterpreter, vulgo
> #!/usr/bin/python3

Deswegen ruft der TO das Teil explizit mit "python3 <skript>" auf, dann 
braucht er keine Shebang-Zeile. Ansonsten wäre "#!/usr/bin/env python3" 
oder "#!/usr/bin/env python" besser, dann funktioniert die Veranstaltung 
auch in virtualenv, venv und Ähnlichem.

von Ein T. (ein_typ)


Lesenswert?

Manfred schrieb:
>
1
>     fp = open('/home/pi/index/ramdisk/temps.txt', 'a')
2
>     fp.write(ClientData.decode('utf-8') + ' ' + time.strftime('%H:%M', 
3
> time.gmt$
4
>     fp.close()
5
>

Der Pythonische Weg wäre der Context Manager, den open() zurückgibt:
1
    with open('/home/pi/index/ramdisk/temps.txt', 'a') as ofh:
2
        ofh.write(ClientData.decode('utf-8') + ...)

Damit wird die Datei automatisch geschlossen. Wohlerzogene Pythonistas 
verzichten in Variablennamen auf Großbuchstaben, statt ClientData würde 
Deine Variable nach PEP8 [1] client_data heißen. Leider halten sich 
einige alten Python-Standardmodule wie das unten erwähnte 
"logging"-Modul nicht vollständig an PEP8, sonst würde die Funktion 
basicConfig basic_config heißen. Aber für neuen Code sollte man sich an 
PEP8 halten, das macht das Leben deutlich einfacher und verhindert, daß 
ein Mob mit Mistforken und Fackeln einen verfolgt. ;-)

Aber eigentlich willst Du ja ein Logging, dafür gibt es ein 
Standardmodul mit dem total überraschenden Namen "logging":
1
import sys
2
import logging
3
4
logging.basicConfig(
5
  format='%(asctime)s %(levelname)s %(message)s',
6
  level=logging.DEBUG,
7
  stream=sys.stderr # oder filename='temps.txt'
8
)
9
10
logging.info('Eine Logmessage mit dem Loglevel INFO')

Achtung: die Übergabe von Parameter an die logging-Funktionen geht etwas 
anders als bei print(), die "faule Auflistung" mit Kommata ist in den 
logging-Funktionen nicht möglich.

[1] https://peps.python.org/pep-0008/

von Manfred (Gast)


Lesenswert?

@ Thomas

Das Verzeichnis /home/pi/index/ramdisk existiert.


@ Ernst

python3 /home/pi/python/UDPServer.py > /home/pi/udp-server.log
Die Log-Datei ist leer (0 Bytes).


@ Axel

python3 /home/pi/python/UDPServer.py >udp.log 2>&1 &

Ergebnis, die Log-Datei enthält diese Fehlermeldung:
1
Traceback (most recent call last):
2
  File "/home/pi/python/UDPServer.py", line 15, in <module>
3
    sock.bind((UDPServerIP, UDPServerPort))
4
OSError: [Errno 99] Cannot assign requested address

Im Startbeitrag ist nicht der Server sondern das Python-Skript des 
Clients gepostet.

Hier das Skript des UDP-Servers:
1
#!/usr/bin/python3
2
3
## Raspberry UDPServer
4
5
import socket
6
7
import time
8
9
UDPServerIP   = '192.168.178.29'
10
11
UDPServerPort = 8086
12
13
ServerACK = b'ACK'
14
15
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
16
17
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
18
19
sock.bind((UDPServerIP, UDPServerPort))
20
21
print('Warte ...')
22
23
while True:
24
25
    ClientData, ClientAddr = sock.recvfrom(1024)
26
27
    sock.sendto(ServerACK, ClientAddr)
28
29
    print(ClientData, ':', ClientAddr)
30
31
    fp = open('/home/pi/index/ramdisk/temps.txt', 'a')
32
33
    fp.write(ClientData.decode('utf-8') + ' ' + time.strftime('%H:%M', time.gmt$
34
35
    fp.close()

von Manfred (Gast)


Lesenswert?

Es funktioniert.

Zwischen Aufruf des Webservers und des UDP-Servers habe ich ein
1
Sleep 100

eingefügt.

In einem Beitrag auf stackoverflow.com stand, dass das der Fehler 
auftritt, wenn noch nicht alle Ressourcen von Linux freigegeben wurden. 
Hier noch einmal die Fehlermeldung:
1
Traceback (most recent call last):
2
  File "/home/pi/python/UDPServer.py", line 15, in <module>
3
    sock.bind((UDPServerIP, UDPServerPort))
4
OSError: [Errno 99] Cannot assign requested address

Wer noch einen Hinweis auf den Fehlergrund hat, poste ihn bitte.

von Εrnst B. (ernst)


Lesenswert?

Manfred schrieb:
> Wer noch einen Hinweis auf den Fehlergrund hat, poste ihn bitte.

Du bindest den UDP-Server an eine spezifische IP-Adresse, nicht an 
0.0.0.0?

Dann muss diese IP-Adresse auch vorhanden sein, wenn der Server startet.
Falls dein RasPi seine IP per DHCP bekommt, oder das AtBoot.sh sehr früh 
startet, ist das nicht gegeben.


Schau dir nochmal an, was oben zum Thema systemd-unit geschrieben wurde. 
Damit kannst du solche Abhängigkeiten und damit die Startreihenfolge 
elegant festlegen.
1
[Unit]
2
Description=Mein UDP Server
3
After=network.target
4
5
[Service]
6
Type=simple
7
User=xyz
8
WorkingDirectory=/home/xyz/udpserver
9
ExecStart=/usr/bin/python3 UDPServer.py
10
...

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.