Alternative für Socket

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Metaln00b
User
Beiträge: 4
Registriert: Mittwoch 20. September 2017, 09:05
Wohnort: Westerwald

Hallo Allerseits,

ich stehe aktuell vor einem Problem.

Ich möchte die Befehlssyntax eines Controllers mithilfe des "unittest" testen. Es umfasst ca 200 Befehle, welche der Reihe nach versendet und mit RegExp verglichen werden.

STX/ETX wird beim Empfangen verwendet. Gesendet wir klassisch mit "\n"

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import sys
import time

class ControllerSocket:
    RECEIVE_CHUNK = 240
    socket.setdefaulttimeout(20)

    def __init__(self, sock=None):
        if sock is None:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        else:
            self.sock = sock

    def connect(self, host, port):
        try:
            self.sock.connect((host, port))
        except socket.error as msg:
            self.sock.close()
            self.sock = None

    def disconnect(self):
        self.sock.shutdown(socket.SHUT_RDWR)
        if self.sock is None:
            print("\nConnection problem\n")
            return
        else:
            self.sock.shutdown(1)
            self.sock.close()
            self.sock = None

    def send(self, message):
        self.sock.sendall(message+"\n")

    def receive(self):
        data = ""
        while not data.endswith("\x03"):
            try:
                data += self.sock.recv(self.RECEIVE_CHUNK)
                time.sleep(0.5)
                print (data)
                print ("Received")
                if(len(data) == 0):
                    print("Mist")
                    self.duplex(message)
                    self.disconnect()
                    return
                time.sleep(0.05)
            except socket.error as msg:
                print msg
                self.disconnect()

        return data[1:-1]

    def duplex(self, message):
        self.send(message)
        return self.receive()
Manchmal habe ich das Problem, dass rein zufällig nichts mehr empfangen wird. Die Verbindung wird unterbrochen, der Controller hängt sich auf, und über Wireshark ist ein "RST" zu erkennen *Connection reset by peer*. Mache das über die VMware, worauf ich es mal unter Windows und einem Stock Linux probiert habe, leider ohne Erfolg.

Dann las ich das hier: LINK

Nun mit time.sleep kam ich leider auch nicht wirklich weiter, es machte es zwar etwas zuverlässiger, aber dennoch unterbricht die Verbindung oder ich empfange einfach nichts mehr.

Jetzt suchte ich nach einer Alternative. Ich kam auf "Twisted".
Senden bekam ich bisher hin, leider scheitere ich am empfangen der Rückmeldung.

Habe den folgenden Code als Test in Verwendung:

Code: Alles auswählen

import sys
from twisted.python import log
from twisted.internet import reactor, interfaces
from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol, Factory
from twisted.application.service import Service, Application

class EchoServerProtocol(Protocol):
    def dataReceived(self, data):
        log.msg('Data received {}'.format(data))
        self.transport.write(data)

    def connectionMade(self):
        log.msg('Client connection from {}'.format(self.transport.getPeer()))

    def connectionLost(self, reason):
        log.msg('Lost connection because {}'.format(reason))


class EchoClientProtocol(Protocol):
    def dataReceived(self, data):
        #while not (data.endswith("\03")):
        log.msg('Data received {}'.format(data))
        self.transport.loseConnection()

    def connectionMade(self):
        data = 'BEFEHL\n'
        self.transport.write(data.encode())
        log.msg('Data sent {}'.format(data))

    def connectionLost(self, reason):
        log.msg('Lost connection because {}'.format(reason))


class EchoServerFactory(ServerFactory):
    def buildProtocol(self, addr):
        return EchoServerProtocol()


class EchoClientFactory(ClientFactory):
    def startedConnecting(self, connector):
        log.msg('Started to connect.')

    def buildProtocol(self, addr):
        log.msg('Connected.')
        return EchoClientProtocol()

    def clientConnectionLost(self, connector, reason):
        log.msg('Lost connection. Reason: {}'.format(reason))

    def clientConnectionFailed(self, connector, reason):
        log.msg('Lost failed. Reason: {}'.format(reason))


def main():
    log.startLogging(sys.stdout)
    log.msg('Start your engines...')

    #reactor.listenTCP(5025, EchoServerFactory())
    reactor.connectTCP('192.168.1.5', 5025, EchoClientFactory())

    reactor.run()


if __name__ == '__main__':
    main()
Rückmeldung sieht dann so aus:

Code: Alles auswählen

2017-09-20 10:25:56+0200 [-] Log opened.
2017-09-20 10:25:56+0200 [-] Start your engines...
2017-09-20 10:25:56+0200 [-] Starting factory <__main__.EchoClientFactory instance at 0x9a08f4c>
2017-09-20 10:25:56+0200 [-] Started to connect.
2017-09-20 10:25:56+0200 [Uninitialized] Connected.
2017-09-20 10:25:56+0200 [Uninitialized] Data sent Befehl
	
2017-09-20 10:25:56+0200 [EchoClientProtocol,client] Data received  <--- \02
2017-09-20 10:25:56+0200 [EchoClientProtocol,client] Lost connection because [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
	]
2017-09-20 10:25:56+0200 [EchoClientProtocol,client] Lost connection. Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
	]
2017-09-20 10:25:56+0200 [-] Stopping factory <__main__.EchoClientFactory instance at 0x9a08f4c>
^C2017-09-20 10:25:58+0200 [-] Received SIGINT, shutting down.
2017-09-20 10:25:58+0200 [-] Main loop terminated.
Hat jemand für mich eine Lösung? Vielleicht eine andere Socket Library oder eine weitere Alternative?
Möchte den Code so einfach wie möglich halten, dass sehe ich bei Twisted bisher nicht.

Auch ein Problem mit der Verständis habe ich beim Befehl listenTCP, da wir nur der Port angegeben, welcher sich ja bei jeder Verbindung ändert.

Vielen Dank im Vorraus

LG
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Metaln00b: der verlinkte Beitrag ist mir sehr suspekt und hat mit Deinem Problem wahrscheinlich nichts zu tun. Du setzt ein timeout von 20 Sekunden, da sollte es eigentlich keine Probleme geben, dass irgendwelche Nachrichten nicht schnell genug gelesen werden.

Apropo: das Setzen des timeouts hat in der Klassendefinition nichts zu suchen. Das Setzen von SO_REUSEADDR macht nur bei Servern Sinn, weg damit. Schlägt der connect fehl, sollte der Aufrufer die Fehlermeldung bekommen, ein close ist da unsinnig. Bei disconnent reicht ein close, weil das ein shutdown impliziert. Falls self.sock im if None ist, ist das Programm schon eine Zeile früher ausgestiegen. Was sollen eigentlich die sleep-Aufrufe in receive? Entweder es sind Daten da, dann liefert recv diese, wenn nicht, dann blockiert es solange, bis welche da sind. Wenn recv keine Daten liefert, heißt das, irgendetwas hat die Verbindung unterbrochen. Der eigene Rechner ist das meist nicht, sondern entweder die Gegenseite oder der Übertragungsweg. Zeile 49 liefert einen NameError, Daten senden zu wollen, wenn die Verbindung abgebrochen ist, macht irgendwie auch keinen Sinn.
Der try-except-Block ist auch unsinnig, weil man am besten einen Fehler, den man nicht sinnvoll verarbeiten kann, einfach auf die nächst höhere Ebene durchläßt (zumal das Programm dann mit einem AttributeError abbricht, der nichts mit dem eigentlichen Fehler zu tun hat).

Wie sieht denn das Protokoll genau aus? Ist das wirklich Half-Duplex? Kommen nach dem ETX keine Bytes mehr, bis wieder eine neue Nachricht gesendet wird? Kannst Du mal ein Beispiel-Output posten, was gesendete und empfangen wird? An welcher Stelle hängt das System?

Code: Alles auswählen

import socket

socket.setdefaulttimeout(20)

class ControllerSocket:
    RECEIVE_CHUNK = 240

    def __init__(self, sock=None):
        if sock is None:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            self.sock = sock

    def connect(self, host, port):
        self.sock.connect((host, port))

    def disconnect(self):
        self.sock.close()

    def send(self, message):
        self.sock.sendall(message + "\n")

    def receive(self):
        data = ""
        while True:
            received = self.sock.recv(self.RECEIVE_CHUNK)
            if not received:
                raise RuntimeError("unexpected end-of-message", data)
            data += received
            print("Received: {!r}".format(data))
            if "\x03" in received:
                break
        start = data.index("\x02")
        stop = data.index("\x03")
        if data[:start]:
            print("Chunk in front of message: {!r}".format(data[:start]))
        if data[stop+1:]:
            print("Chunk after message: {!r}".format(data[stop+1:]))
        return data[start+1:stop]

    def duplex(self, message):
        self.send(message)
        return self.receive()
Metaln00b
User
Beiträge: 4
Registriert: Mittwoch 20. September 2017, 09:05
Wohnort: Westerwald

Vielen Dank für die schnelle Rückmeldung.

Code: Alles auswählen

    received = self.sock.recv(self.RECEIVE_CHUNK)
error: [Errno 104] Connection reset by peer
Das oben genannte erscheint ohne time.sleep sehr oft, auch mit deinem Code. Das Problem lässt sich auch nicht gescheit reproduzieren. Mal läuft es komplett durch, mal nicht.

Wenn ich den Befehl z.B. "temp?\n" sende kommt wirklich "\x0222.5\n\0x03" danach ist Schicht. Schicke ich den Befehl sehr oft hintereinander, kommt es gelegentlich zu einem Connection Reset und der Controller hängt. Dieses Problem tritt seltener auf, wenn ich time.sleep verwende. Bei meinem Code war es auch so, dass gelegentlich mal *nichts* zurück kam und ich die ganze Zeit "Received: *nix*" erhielt. Ich verwende das TCP Protokoll. Auf dem Controller läuft ein Telnet-Server.

Jetzt habe ich den Controller neu gestartet und nochmal probiert jetzt läuft es eine Zeit lang durch. Bekomme gelegentlich:

Code: Alles auswählen

    received = self.sock.recv(self.RECEIVE_CHUNK)
error: [Errno 104] Connection reset by peer
Unter Windows:

Code: Alles auswählen

error: [Errno 10054] Eine vorhandene Verbindung wurde vom Remotehost geschlossen
Deine if-Anweisungen Data Start und Stop, scheinen bei mir keine Wirkung zu haben...

LG
Metaln00b
User
Beiträge: 4
Registriert: Mittwoch 20. September 2017, 09:05
Wohnort: Westerwald

Möchte mich kurz korrigieren (sry für den Doppelpost).

Dein Code scheint doch so einiges verbessert zu haben.

Nach 5maligen schicken eines Befehls, trenne ich die Verbindung, baue direkt wieder eine Verbindung auf und schicke 5mal einen anderen Befehl.
Nach Trennen und direktem Wiederaufbauen der Verbindung, schickt der !Controller! ein RST. Wenn ich allerdings time.sleep(0.5), nach dem disconnect warte, läuft die Kiste =) Bisher auch ohne Probleme
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Metaln00b: dann hat Deine Kiste Probleme wenn zu schnell hintereinander Befehle geschickt oder Verbindungen aufgebaut werden. Dann solltest Du da Pausen machen und nicht beim recv.
Metaln00b
User
Beiträge: 4
Registriert: Mittwoch 20. September 2017, 09:05
Wohnort: Westerwald

Sirius3 hat geschrieben:@Metaln00b: dann hat Deine Kiste Probleme wenn zu schnell hintereinander Befehle geschickt oder Verbindungen aufgebaut werden. Dann solltest Du da Pausen machen und nicht beim recv.

Code: Alles auswählen

    def disconnect(self):
        self.sock.close()
        time.sleep(0.5)
Alles klar. Ich danke dir :mrgreen: vielmals für deine Hilfe, du hast mir sehr viel geholfen. Ich wünsche dir einen angenehmen Tag :wink:

PS: Schade dass man kein Danke-Button hat...
Antworten