zwei mal resv() im Programm.

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
thomas223
User
Beiträge: 12
Registriert: Donnerstag 29. Juni 2017, 07:35

Hallo,
Ich weiß mein code ist sicher nicht professionell, fange ja auch gerade erst an.
Das Programm verbindet sich mit einem Server, und bekommt von Ihm permanent Strings die ich in der Schleife auch weiter verarbeite, dass läuft auch super.
Nur soll sobald der Button gedrückt wird der String aus dem Eingabefeld gesendet werden und hiernach kommt nicht der übliche String zurück, sondern ob dieser verarbeitet wurde oder nicht.
wie bekomme ich es hin das die Schleife empfangedaten soweit fürs erste unterbrochen wird?
oder muss ich da einen anderen Weg gehen?
Danke für Hilfe!

Code: Alles auswählen

from tkinter import *
import socket
import threading

ip = "192.168.0.20" #input("IP-Adresse: ") 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect_ex((ip, 2112))
s.setblocking(1)
print ("Conneckt")


def empfangedaten():
  while(1):  
    antwort = s.recv(1024)
    print (str (antwort) +"falscher")

def sendedaten():
      print ("sende daten")
      #Hole daten von Eingabefeld
      prod = e1.get()
      s.send(bytes (prod,'ascii'))
      antwort = s.recv(1024)
      print(antwort)

fenster=Tk()

fenster.title("RFID Schreib-Leseeinheit")

e1 = Entry(fenster)
e1.grid(row=4, column=1)

button = Button(fenster, text='Übernemen', width=25, command=sendedaten)
button.grid(row=7,column=1)

threading.Thread(target=empfangedaten).start()
fenster.mainloop()
:K
Zuletzt geändert von Anonymous am Donnerstag 20. Juli 2017, 13:33, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@thomas223: Das geht so nicht. Von zwei Threads aus gleichzeitig `recv()` aufzurufen ist ein Glücksspiel welcher von beiden welche Daten empfängt. Zudem schreibst Du etwas von Zeilen ohne tatsächlich dafür zu sorgen das ganze Zeilen gelesen werden. So ein `recv()`-Aufruf liefert 1 bis 1024 Bytes aus dem Datenstrom. Dabei muss das nichts damit zu tun haben welche dieser Daten auf der anderen Seite zusammen gesendet wurden. Du musst so oft `revc()`-Aufrufen bis Du mindestens eine Zeile zusammen hast. Und Dir merken falls da auch schon etwas von der nächsten Zeile ausgelesen wurde und das für die Zukunft merken und berücksichtigen.

Umgekehrt sendet `send()` nicht zwingend alles was Du übergibst. Die Methode hat als Rückgabewert die Anzahl der tatsächlichen gesendeten Bytes und man muss die so lange mit den noch nicht gesendeten Bytes aufrufen bis wirklich alles raus ist. Oder man verwendet `sendall()` — das macht das schon für einen.

Was man auch machen kann ist sich von dem Socket-Objekt ein Dateiobjekt geben lassen. Von dem kann man dann beispielsweise mit der `next()`-Funktion die jeweils nächste Zeile abfragen.

Warum `connect_ex()` und nicht einfach `connect()` und warum `setblocking()`?

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Da dann keine Variablen mehr auf Modulebene stehen können die Funktionen auch nicht mehr so ”magisch” auf Werte aus der ”Umgebung” zugreifen sondern müssen alle Werte, ausser Konstanten, als Argument(e) übergeben bekommen. Was das Programm leichter nachvollziebar und testbar macht. Für GUIs ergibt sich dadurch ausserdem, das man ohne objektorientierte Programmierung nicht weit kommt.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@thomas223: wie BlackJack schon geschrieben hat, darf es nur einen Thread zum Lesen geben. Die Kommunikation mit diesem Thread kann über Queues erfolgen. Am besten wird der Socket auch in diesem Thread erzeugt, dann kann nicht aus versehen von woanders darauf zugegriffen werden.

Daneben sind Sternchenimporte böse™, weil nicht kontrolliert werden kann, was da in den eigenen Namensraum geladen wird. Eingerückt wird immer mit 4 Leerzeichen pro Ebene. while ist keine Funktion, da braucht es also keine Klammern; es gibt True
thomas223
User
Beiträge: 12
Registriert: Donnerstag 29. Juni 2017, 07:35

Ich weiß mein Code sieht teilweise echt schlimm aus. Ich bin kein EDVler ich bin Heizungsbauer und ich denke wenn ich euch ein Rohr in die Hand geben würde sähe es danach ähnlich aus. :lol:
Das Ist das erste Projekt in einer Hochsprache für mich drum bitte ich um Nachsicht.
Ich hab das jetzt mal geändert und eigentlich funktioniert es jetzt wie es soll.

Code: Alles auswählen

ip = "192.168.0.20" #input("IP-Adresse: ")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, 2112))
#s.setblocking(1)
print ("Conneckt")
 
 
def empfangedaten():
  while(1):
          global antwort
          global flagge
          antwort = s.recv(1024)
          print (str (antwort) +"falscher")
          flagge=1
          #print (flagge)
          
    
 
def sendedaten():
      print ("sende daten")
      global antwort
      global flagge
      flagge=0
      #Hole daten von Eingabefeld
      prod = e1.get()
      s.sendall(bytes (prod,'ascii'))
      #antwort = s.recv(1024)
      #print (len(antwort))
      while flagge == 0:
        print('Wartet')
        time.sleep(0.1)
      print (antwort)  
      print("Nächste Anweisung")
 
fenster=Tk()
 
fenster.title("RFID Schreib-Leseeinheit")
 
e1 = Entry(fenster)
e1.grid(row=4, column=1)
 
button = Button(fenster, text='Übernemen', width=25, command=sendedaten)
button.grid(row=7,column=1)
 
threading.Thread(target=empfangedaten).start()
fenster.mainloop()
Was ich mir noch wünschen würde, wäre das er auf eine Verbindung warten würde und mir eine Rückmeldung gibt ob oder ob nicht verbunden.
Zuletzt geändert von Anonymous am Freitag 21. Juli 2017, 09:19, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@thomas223: wenn Du neben mir stehst und ich versuche ein Rohr mit dem Hammer an die Wand zu nageln, dann wirst Du auch was sagen, und wenn Du später in meinen Keller kommst und siehst, dass die Gasleitungen per Isolierband zusammengeklebt wurden, kannst Du nicht still bleiben, auch wenn ich sage "funktioniert doch, der Ofen heizt".
Genauso ist das mit Deinem Code (nur dass dabei keine Häuser in die Luft fliegen). Der Code funktioniert zufällig, manchmal, garantiert nicht immer. Was ist denn das für ein Server? Was sendert der? Wann? Warum liest Du ständig, verwendest die Antwort aber nur bei bestimmten Ereignissen?
thomas223
User
Beiträge: 12
Registriert: Donnerstag 29. Juni 2017, 07:35

Da hast du wohl recht mit dem Klebeband.
Der Code hier ist auch nur ein Schnipsel.
Der Server ist ein RFID Lese- Schreibgerät.

der bekommt bei sendedaten: sMN SetAccessMode 3 7A99FDC6
dann bekomme ich eine Antwort: b'\x02sAN SetAccessMode 1\x03'
dann sende ich soetwas: sWN UCRWCfg 2 5 0 21 7 24 00 57 44 30 30 32 31 31 41 00 32 30 32 30 2d 30 37 00 30 30 33 32 38 37 41 50 30 36 31 30 FF 4 0 1 7
Antwort: b'\x02sWA UCRWCfg\x03
Sende: sMN mEEwriteall
Antwort: b'\x02sAN mEEwriteall 1\x03'
Sende: sMN Run
Antwort: b'\x02sAN Run 1\x03'

Diese schritte werden einmal durchlaufen um ihm die Parameter die er schreiben soll zu übergeben.

Wenn jetzt ein Transponder beschrieben wurde bekomme ich für jeden die geschriebenen Parameter zurück. Diese möchte ich später dann weg schreiben.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@thomas223: Bei Parallelposts ist es immer nett, wenn man den Lesern die Chance gibt, dass an anderer Stelle vielleicht schon Antworten auf das selbe Problem stehen.

Und wenn Du hier, wie dort die selbe Antwort bekommst, ist vielleicht was dran. Du bekommst also nur eine Antwort, wenn Du auch was gesendet hast. Warum hast Du dann einen Lese-Thread? Wo ist die Dokumentation zu diesem Lesegerät. Konnte gerade bei Google nichts finden.

Es scheint so, als ob Antworten immer mit \x02 anfangen und mit \x03 aufhören. Dafür mußt Du ein Protokoll schreiben, weil recv nicht unbedingt immer genau einen Block mit \x02...\x03 empfängt. Wie werden die gesendeten Daten kodiert? Da scheint mir so etwas wie ein Anfangs- und End-Byte wie bei den Antworten zu fehlen.
thomas223
User
Beiträge: 12
Registriert: Donnerstag 29. Juni 2017, 07:35

Ja beim senden muss ich die Steuerzeichen 0x02 und 0x03 ebenfalls mit liefern, das klappt auch. sieht dann so aus.
b'\x02sWN UCRWCfg 2 5 0 21 7 24 00 74 65 73 74 00 00 FF 4 0 1 7\x03'

Ich sende nicht ständig, nur wenn sich die zu schreibenden Daten ändern sollen.
Die Transponder werden von dem Gerät dann mit den Daten selbstständig beschrieben. Immer und immer wieder und für jeden Schreibprozess bekomme ich die geschriebenen Daten, deshalb der Thread.

So war meine Überlegung. Ich denke ich klammere mich auch zu sehr an meinem Code. Doch wie kann ich das Besser machen?

Aufgabe ist:
Verbindung herstellen,
Benutzeroberfläche erstellen.
Die Strings senden, also senden auf Antwort warten nächsten senden usw. bis fertig.
Geschriebene Daten einsammeln anzeigen und in Datei oder Datenbank schreiben.

das senden der Strings kann zwischendurch noch mal geschehen.
Wie fange ich zur Not neu an? um alles besser zu machen.
BlackJack

@thomas223: Also mir ist immer noch nicht klar ob das Gerät selbstständig, unaufgefordert etwas sendet. Denn Du beschreibst im Grunde ein Halbduplex-Protokoll und sagst dann *deswegen* hast Du einen separaten Lesethread, wobei Halbduplex etwas ist, bei dem man schreiben und lesen im gleichen Thread machen kann, weil es eben nicht unabhängig voneinander ist. Es kann Sinn machen das in einen Thread auszulagern, also schreiben *und* lesen, um die GUI nicht zu blockieren, aber das Programm wäre auch dann noch weniger komplex.

Ich würde die Programmlogik und die GUI trennen. Also erst einmal Funktionen, beziehungsweise wahrscheinlich schon eine Klasse schreiben, die die Kommunikation/das Protokoll kapselt und eine `read()`-Methode zum lesen der nächsten Nachricht und eine `write()`-Methode zum senden einer Nachricht bietet.

Schreiben ist mit dem einbetten in die Start- und Endbytes und `sendall()` ja relativ einfach.

Das lesen ist dann aber schon etwas worüber man ein bisschen nachdenken muss, damit man alle Fälle abdeckt. Falls es Halbduplex ist, dann ist es etwas einfacher, denn dann muss man nur solange `recv()` aufrufen bis das Endbyte enthalten ist (was nur am Ende sein dürfte) und eventuell alles bis zum ersten Startbyte verwerfen (und eine Warnung ausgeben falls da tatsächlich etwas ist).

Wenn das Gerät auch unaufgefordert Nachrichten senden kann, dann wird es komplexer, denn dann muss man nicht nur solange lesen bis mindestens eine Nachricht komplett ist, sondern auch eventuelle zusätzliche Nachrichten oder Teilnachrichten für den nächsten Leseaufruf merken und dort dann berücksichtigen.

Wenn man so ein generisches `read()` und `write()` hat, kann man protokollspezifische Methoden schreiben wie `set_access_mode()`, `run()` und ähnliches.

Und ganz wichtig: Vergiss das es ``global`` gibt. Das schafft letztendlich mehr Probleme als es löst. Insbesondere in Verbindung mit Threads will man das überhaupt nicht verwenden.
thomas223
User
Beiträge: 12
Registriert: Donnerstag 29. Juni 2017, 07:35

Das ist das Problem, es wird ein mal diese Abfolge gesendet und empfangen und dann vielleicht 20000 mal nur empfangen.
Das Senden und das empfangen klappt super, nur beides zusammen in einem Programm da hänge ich mich dran auf?
beim Senden muss ich halt auf die Rückmeldung vom Gerät warten, die Nachrichten die zum Sendeprozess gehören kann ich auch gut separieren. mit einer While schleife warte ich bis die richtige Antwort von Thread kommt, das funktioniert auch aber nur ein mal?
Ich weiß noch nicht warum ich beim zweiten mal sich der Thread anscheinend ab schaltet?
BlackJack

@thomas223: Warum sich der Thread beim zweiten mal ”abschaltet” kann man ohne zu wissen was Du da genau machst, nur raten. Ich würde eventuell raten das die Schleife nicht noch einmal in einer Schleife steckt und damit der Code im Thread einfach das ”natürliche Ende” erreicht und der Thread damit eben am Ende ist. Und Threads kann man nur einmal starten. Wenn ein Thread einmal gelaufen ist, muss man einen neuen erzeugen wenn man den Code noch einmal nebenläufig ausführen möchte. Ein weiterer `start()`-Aufruf wurd auch mit einer entsprechenden und eigentlich sehr aussagekräftigen Ausnahme quittiert:

Code: Alles auswählen

In [2]: t = threading.Thread()

In [3]: t.start()

In [4]: t.start()
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-4-82d529646f21> in <module>()
----> 1 t.start()

/usr/lib/python2.7/threading.pyc in start(self)
    486             raise RuntimeError("thread.__init__() not called")
    487         if self.__started.is_set():
--> 488             raise RuntimeError("threads can only be started once")
    489         if __debug__:
    490             self._note("%s.start(): starting thread", self)

RuntimeError: threads can only be started once
Es sieht so aus als müsstest Du tatsächlich ständig empfangen und die Nachrichten verwerfen. Und wenn Du dann eine Antwort auswerten möchtest, dann könntest Du vor dem Senden der Anfrage dem Thread mitteilen das die Antworten ab sofort wichtig sind und in eine Queue gesteckt werden sollen. Nach der Anfrage kannst Du diese Queue dann solange abarbeiten bis Du die Antwort verarbeitet hast, dem Thread sagen er kann die weiteren Antworten erst einmal wieder ignorieren, und den Rest in der Queue verwerfen.

Zum Signalisieren das die gelesenen Nachrichten verworfen werden sollen oder nicht, könnte man ein `threading.Event` verwenden.
Antworten