it-swarm.com.de

Python-Socket-Empfang - eingehende Pakete haben immer eine andere Größe

Ich verwende das SocketServer-Modul für einen TCP -Server .. __ Ich habe hier ein Problem mit der recv()-Funktion, da die eingehenden Pakete immer eine andere Größe haben, wenn ich also recv(1024) (ich habe es mit versucht) angeben ein größerer Wert und kleiner), bleibt er nach 2 oder 3 Anforderungen hängen, da die Paketlänge kleiner wird (denke ich), und der Server bleibt bis zu einem Timeout hängen.

class Test(SocketServer.BaseRequestHandler):

def handle(self):

   print "From:", self.client_address

   while True:    

     data = self.request.recv(1024)
     if not data: break

     if data[4] == "\x20":              
       self.request.sendall("hello")
     if data[4] == "\x21":
       self.request.sendall("bye")
     else:
       print "unknow packet"
   self.request.close()
   print "Disconnected", self.client_address

launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test)

launch.allow_reuse_address= True;

launch.serve_forever()

Wenn der Client mehrere Anfragen über denselben Quellport sendet, der Server jedoch stecken bleibt, wäre jede Hilfe sehr dankbar, danke! 

29
n00bie

Das Netzwerk ist immer unvorhersehbar. TCP bewirkt, dass viele von diesem zufälligen Verhalten für Sie verschwinden. Eine wunderbare Sache TCP tut: Es garantiert, dass die Bytes in der gleichen Reihenfolge ankommen. Aber! Es tut k Garantie, dass sie auf die gleiche Art und Weise zerhackt werden ankommen. Sie setzen einfach kann nicht davon aus, dass jedes send () von einem Ende der Verbindung zu genau einem recv () am fernen Ende mit genau derselben Anzahl von Bytes führt. 

Wenn Sie socket.recv(x) sagen, sagen Sie 'kehrt nicht zurück, bis Sie x Bytes aus dem Socket gelesen haben'. Dies wird als "blockierende E/A" bezeichnet: Sie werden blockieren (warten), bis Ihre Anforderung erfüllt ist. Wenn jede Nachricht in Ihrem Protokoll genau 1024 Byte groß wäre, würde der Aufruf von socket.recv(1024) gut funktionieren. Das klingt aber so, als ob das nicht stimmt. Wenn es sich bei Ihren Nachrichten um eine feste Anzahl von Bytes handelt, übergeben Sie diese Anzahl einfach an socket.recv() und Sie sind fertig.

Was aber, wenn Ihre Nachrichten unterschiedlich lang sein können? Als erstes müssen Sie socket.recv() mit einer expliziten Nummer nicht mehr aufrufen. Dies ändern:

data = self.request.recv(1024)

zu diesem:

data = self.request.recv()

bedeutet, dass recv() immer zurückkehrt, wenn neue Daten eingehen.

Jetzt haben Sie jedoch ein neues Problem: Woher wissen Sie, wann der Absender Ihnen eine vollständige Nachricht gesendet hat? Die Antwort ist: Sie tun es nicht. Sie müssen die Länge der Nachricht zu einem expliziten Bestandteil Ihres Protokolls machen. Dies ist der beste Weg: Stellen Sie jeder Nachricht eine Länge voran, entweder als Ganzzahl mit fester Größe (konvertiert in die Netzwerk-Byte-Reihenfolge mit socket.ntohs() oder socket.ntohl() please!) Oder als Zeichenfolge gefolgt von einem Trennzeichen (wie '123:'). Dieser zweite Ansatz ist oft weniger effizient, in Python jedoch einfacher.

Wenn Sie dies zu Ihrem Protokoll hinzugefügt haben, müssen Sie Ihren Code so ändern, dass recv() jederzeit beliebige Datenmengen zurückgibt. Hier ist ein Beispiel, wie das geht. Ich habe versucht, es als Pseudo-Code zu schreiben oder mit Kommentaren zu sagen, was zu tun ist, aber es war nicht sehr klar. Also habe ich es explizit geschrieben und das Längenpräfix als eine Zeichenfolge mit einem Doppelpunkt verwendet. Bitte schön:

length = None
buffer = ""
while True:
  data += self.request.recv()
  if not data:
    break
  buffer += data
  while True:
    if length is None:
      if ':' not in buffer:
        break
      # remove the length bytes from the front of buffer
      # leave any remaining bytes in the buffer!
      length_str, ignored, buffer = buffer.partition(':')
      length = int(length_str)

    if len(buffer) < length:
      break
    # split off the full message from the remaining bytes
    # leave any remaining bytes in the buffer!
    message = buffer[:length]
    buffer = buffer[length:]
    length = None
    # PROCESS MESSAGE HERE
36
Larry Hastings

Die Antwort von Larry Hastings enthält einige großartige allgemeine Hinweise zu Sockets. Es gibt jedoch einige Fehler, die sich auf die Funktionsweise der recv(bufsize)-Methode im Python-Socket-Modul beziehen.

Um dies zu verdeutlichen, kann dies für andere verwirrend sein, die Hilfe suchen:

  1. Der Parameter bufsize für die Methode recv(bufsize) ist nicht optional. Sie erhalten eine Fehlermeldung, wenn Sie recv() (ohne den Parameter) aufrufen.
  2. Der Puffer in recv(bufsize) hat eine maximum -Größe. Der Recv gibt weniger Bytes zurück, wenn weniger verfügbar sind.

Siehe die Dokumentation für Details.

Wenn Sie nun Daten von einem Client erhalten und wissen möchten, wann Sie alle Daten erhalten haben, müssen Sie sie wahrscheinlich zu Ihrem Protokoll hinzufügen - wie Larry empfiehlt. Siehe dieses Rezept für Strategien zur Bestimmung des Endes der Nachricht.

Wie aus diesem Rezept hervorgeht, wird der Client bei einigen Protokollen einfach die Verbindung trennen, wenn er mit dem Senden der Daten fertig ist. In diesen Fällen sollte Ihre while True-Schleife einwandfrei funktionieren. Wenn der Client die Verbindung mit nicht trennt, müssen Sie einen Weg finden, um die Länge Ihres Inhalts zu signalisieren, Ihre Nachrichten zu begrenzen oder ein Timeout zu implementieren.

Gerne helfe ich Ihnen weiter, wenn Sie Ihren genauen Client-Code und eine Beschreibung Ihres Testprotokolls posten könnten.

120
Hans L

Sie können alternativ recv(x_bytes, socket.MSG_WAITALL) verwenden, was scheinbar nur unter Unix funktioniert und genau x_bytes zurückgibt.

14
henrietta

Das ist die Natur von TCP: Das Protokoll füllt Pakete aus (untere Schicht sind IP-Pakete) und sendet sie. Sie können eine gewisse Kontrolle über die MTU (Maximum Transfer Unit) haben.

Mit anderen Worten: Sie müssen ein Protokoll erstellen, das auf TCP steht, wo Ihre "Payload-Abgrenzung" definiert ist. Mit "Payload Delineation" meine ich die Art und Weise, wie Sie die von Ihrem Protokoll unterstützte Nachrichteneinheit extrahieren. Dies kann so einfach sein wie "alle NULL-terminierten Strings".

2
jldupont

Beachten Sie, dass der genaue Grund , warum Ihr Code eingefroren ist, nicht von Ihnen festgelegt wurde zu hohe request.recv () Puffergröße. Hier wird erklärt Was bedeutet Puffergröße in socket.recv (buffer_size)

Dieser Code funktioniert, bis eine leere TCP Nachricht empfangen wird (wenn Sie diese leere Nachricht drucken würden, es würde zeigen b''):

while True:    
  data = self.request.recv(1024)
  if not data: break

Und beachten Sie, dass es keine Möglichkeit gibt , eine leere TCP Nachricht zu senden. socket.send(b'') hat einfach gewonnen funktioniert nicht.

Warum? Da leere Nachrichten nur gesendet werden, wenn Sie socket.close() eingeben, wird das Skript in einer Schleife ausgeführt, solange Sie die Verbindung nicht trennen. Als wies Hans L hier auf einige gute Methoden zum Beenden der Nachricht hin.

1
Qback

Ich weiß, das ist alt, aber ich hoffe, das hilft jemandem. 

Ich habe festgestellt, dass Sie mithilfe von regulären Python-Sockets Informationen in Paketen mit sendto und recvfrom senden und empfangen können

# tcp_echo_server.py
import socket

ADDRESS = ''
PORT = 54321

connections = []
Host = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Host.setblocking(0)
Host.bind((ADDRESS, PORT))
Host.listen(10)  # 10 is how many clients it accepts

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    for i in reversed(range(len(connections))):
        try:
            data, sender = connections[i][0].recvfrom(1500)
            return data
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)
    return b''  # return empty if no data found

def write(data):
    for i in reversed(range(len(connections))):
        try:
            connections[i][0].sendto(data, connections[i][1])
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)

# Run the main loop
while True:
    try:
        con, addr = Host.accept()
        connections.append((con, addr))
    except BlockingIOError:
        pass

    data = read()
    if data != b'':
        print(data)
        write(b'ECHO: ' + data)
        if data == b"exit":
            break

# Close the sockets
for i in reversed(range(len(connections))):
    close_socket(connections[i][0])
    connections.pop(i)
close_socket(Host)

Der Client ist ähnlich

# tcp_client.py
import socket

ADDRESS = "localhost"
PORT = 54321

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ADDRESS, PORT))
s.setblocking(0)

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    """Read data and return the read bytes."""
    try:
        data, sender = s.recvfrom(1500)
        return data
    except (BlockingIOError, socket.timeout, AttributeError, OSError):
        return b''
    except (ConnectionResetError, ConnectionAbortedError, AttributeError):
        close_socket(s)
        return b''

def write(data):
    try:
        s.sendto(data, (ADDRESS, PORT))
    except (ConnectionResetError, ConnectionAbortedError):
        close_socket(s)

while True:
    msg = input("Enter a message: ")
    write(msg.encode('utf-8'))

    data = read()
    if data != b"":
        print("Message Received:", data)

    if msg == "exit":
        break

close_socket(s)
0
justengel