Python socket receive - les paquets entrants ont toujours une taille différente
j'utilise le module SocketServer pour un serveur TCP.
Je suis confronté à un problème ici avec la fonction recv()
, parce que les paquets entrants ont toujours une taille différente, donc si je spécifie recv(1024)
(j'ai essayé avec une plus grande valeur, et plus petit), il est coincé après 2 ou 3 requêtes parce que la longueur du paquet sera plus petit (je pense), et puis le serveur est coincé jusqu'à un délai.
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()
si le client envoie plusieurs requêtes sur la même port source, mais le serveur est bloqué, toute aide serait très apprécié, merci !
6 réponses
le réseau est toujours imprévisible. TCP fait que beaucoup de ces comportements aléatoires disparaissent pour vous. Une chose merveilleuse que TCP fait: il garantit que les octets arriveront dans le même ordre. Mais! Il ne pas garantie qu'ils arriveront découpés de la même manière. Vous simplement ne peut pas supposer que chaque envoi() à partir d'une extrémité de la connexion se traduira par exactement un recv () à l'extrémité opposée avec exactement le même nombre d'octets.
quand vous dites socket.recv(x)
, vous dites "ne revenez pas tant que vous n'avez pas lu x octets à partir de la socket". Cela s'appelle "bloquer l'entrée/sortie": vous bloquerez (attendez) jusqu'à ce que votre demande ait été remplie. Si chaque message dans votre protocole était exactement 1024 octets, appeler socket.recv(1024)
fonctionnerait très bien. Mais il semble que ce n'est pas vrai. Si vos messages sont un nombre fixe d'octets, il suffit de passer ce nombre à socket.recv()
et vous avez terminé.
mais que faire si vos messages peuvent être de différentes longueurs? La première chose que vous devez faire: arrêter d'appeler socket.recv()
avec un nombre explicite. Changer ceci:
data = self.request.recv(1024)
à ceci:
data = self.request.recv()
signifie recv()
sera toujours de retour chaque fois qu'il obtient de nouvelles données.
Mais vous avez maintenant un nouveau problème: comment savez-vous si l'expéditeur a envoyé un message complet? La réponse est: vous ne le fais pas. Vous allez devoir faire de la longueur du message Une partie explicite de votre protocole. Voici la meilleure façon: préfixez chaque message avec une longueur, soit comme un entier de taille fixe (converti en byte d'ordre de réseau en utilisant socket.ntohs()
ou socket.ntohl()
s'il vous plaît!) ou sous forme de chaîne suivie d'un délimiteur (comme '123:'). Cette seconde approche est souvent moins efficace, mais elle est plus facile en Python.
une fois que vous avez ajouté cela à votre protocole, vous devez changer votre code en gérer recv()
retourner des quantités arbitraires de données à tout moment. Voici un exemple de comment faire cela. J'ai essayé de l'écrire en pseudo-code, ou avec des commentaires pour vous dire quoi faire, mais il n'était pas très clair. Donc je l'ai écrit explicitement en utilisant le préfixe de longueur comme une chaîne de chiffres terminée par deux points. Et voilà:
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
la réponse de Larry Hastings contient quelques bons conseils généraux sur les sockets, mais il y a quelques erreurs en ce qui concerne la façon dont la méthode recv(bufsize)
fonctionne dans le module de socket Python.
donc, pour clarifier, puisque cela peut être déroutant pour d'autres qui cherchent de l'aide:
- le paramètre bufsize pour la méthode
recv(bufsize)
n'est pas facultatif. Vous obtiendrez une erreur si vous appelezrecv()
(sans le param). - le bufferlen dans
recv(bufsize)
est un maximum taille. Le recv renverra volontiers moins d'octets s'il y en a moins disponibles.
voir la documentation pour plus de détails.
maintenant, si vous recevez des données d'un client et que vous voulez savoir quand vous avez reçu toutes les données, vous allez probablement devoir les ajouter à votre protocole -- comme Larry le suggère. Voir ce recette pour les stratégies de détermination de la fin de message.
comme le souligne cette recette, pour certains protocoles, le client se déconnectera simplement quand il aura fini d'envoyer des données. Dans ces cas, votre boucle while True
devrait fonctionner correctement. Si le client ne pas déconnecter, vous aurez besoin de comprendre un moyen de signaler votre longueur de contenu, délimiter vos messages, ou mettre en œuvre un timeout.
je serais heureux d'essayer d'aider de plus, si vous pouviez afficher votre code client exact et une description de votre protocole de test.
vous pouvez alternativement utiliser recv(x_bytes, socket.MSG_WAITALL)
, qui semble fonctionner seulement sur Unix, et retournera exactement x_bytes
.
C'est la nature de TCP: le protocole remplit des paquets (couche inférieure étant des paquets IP) et les envoie. Vous pouvez avoir un certain degré de contrôle sur le MTU (Maximum Transfer Unit).
en d'autres termes: vous devez concevoir un protocole qui chevauche TCP où votre" délimitation de charge utile " est définie. Par "délimitation de la charge utile", je veux dire la façon dont vous extrayez l'Unité de message que votre protocole supporte. Cela peut être aussi simple que "toutes les chaînes à terminaison nulle".
notez que raison exacte pourquoi votre code est gelé est et non parce que vous avez placé une requête trop élevée.Taille du tampon recv (). Ici est expliqué ce qui signifie la taille du tampon dans la socket.recv (buffer_size)
ce code fonctionnera jusqu'à ce qu'il reçoive un message TCP vide (si vous imprimez ce message vide, il affichera b''
):
while True:
data = self.request.recv(1024)
if not data: break
et note, qu'il y a no way pour envoyer un message TCP vide. socket.send(b'')
ne marchera pas.
pourquoi? Parce que le message vide est envoyé seulement quand vous tapez socket.close()
, donc votre script Boucle aussi longtemps que vous ne fermez pas votre connexion.
Comme Hans l fait remarquer ici quelques bonnes méthodes pour terminer le message .
je sais que c'est vieux, mais j'espère que cela aide quelqu'un.
en utilisant des sockets python réguliers j'ai trouvé que vous pouvez envoyer et recevoir des informations dans des paquets en utilisant sendto et recvfrom
# 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)
le client est similaire
# 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)