1
votes

Module socket Python: réponse aux données Recv () coupée

Explication

J'essaie actuellement de contrôler une barrette d'alimentation intelligente à l'aide d'un script python. Pour ce faire, j'utilise une connexion TCP avec le module socket. Environ 75% du temps, j'obtiens la réponse / les données que je recherchais et tout fonctionne parfaitement. Cependant, environ 25% du temps, la réponse est coupée exactement à la même longueur, 1024 octets. Cela n'a aucun sens pour moi, car ma taille de tampon est en fait définie sur 2048 octets. La vitesse à laquelle j'attends entre l'utilisation de recv () ne semble pas non plus affecter / provoquer cela. Bien que TCP soit un flux d'octets, est-il toujours possible que cela ait à voir avec la fragmentation des paquets?

Code

Code principal

def decrypt(string):
    key = 171
    result = ""
    for i in string:
        a = key ^ ord(i)
        key = ord(i)
        result += chr(a)
    return result

Fonction de chiffrement

def encrypt(string):
    key = 171
    result = pack('>I', len(string))
    for i in string:
        a = key ^ ord(i)
        key = a
        result += chr(a)
    return result

Fonction de déchiffrement strong >

ip='192.168.0.62'
port=9999

sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.connect((ip, port))
sock_tcp.send(encrypt('{"system":{"get_sysinfo":{}}}'))
data = sock_tcp.recv(2048)
sock_tcp.close()
print len(data) #On succesful runs output is 1221, on unsuccesful runs it is 1024
rec = decrypt(data[4:])
print str(rec) #See output below

Sortie

La chaîne elle-même que je reçois. Ce n'est probablement pas pertinent, mais j'ai pensé que je l'inclurais quand même. Il s'agit de la valeur de la variable rec.

Sortie souhaitée et régulière

Sortie souhaitée complète

{"system": {"get_sysinfo": {"sw_ver": "1.0.6 Build 180627 Rel.081000 "," hw_ver ":" 1.0 "," model ":" HS300 (États-Unis) "," deviceId ":" 80067B24A755F99C4D6C1807455E09F91AB7B2AA "," oemId ":" 5C9E6254BEBAED63B2B610 "" 5C9E6254BEBAED63B2B610 "" 5C9E6254BEBAED63B2B610 "2966D24817C47C47" ": -60," longitude_i ": - 1222955," latitude_i ": 379078," alias ":" TP-LINK_Power Strip_4F01 "," mic_type ":" IOT.SMARTPLUGSWITCH "," feature ":" TIM: ENE "," mac ":" B0: BE: 76: 12: 4F: 01 "," mise à jour ": 0," led_off " : 0, "children": [{"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA00", "state": 0, "alias": "CezHeat", "on_time": 0, "next_action": {"type": - 1}} , {"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA01", "state": 1, "alias": "CezUVB", "on_time": 191208, "next_action": {"type": - 1}}, {"id": " 80067B24A755F99C4D6C1807455E09F91AB7B2AA02 "," state ": 1," alias ":" CyanHeat "," on_time ": 191208," next_action ": {" type ": - 1}}, {" id ":" 80067B24A7505F99C4D6C1 " 1, "alias": "ZanderHeat", "on_time": 191208, "next_action": {"type": - 1}}, {"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA04", "state": 1, "alias": " CairoHeat "," on_time ": 191208," next_action ": {" type ": - 1}}, {" id ":" 80067B24A755F99C4D6C1807455E09F91AB7B2AA05 "," state ": 1," alias ":" KodaMister "," on_time ": 191208, "next_action": {"type": - 1}}], "child_num": 6, "err_code": 0}}}

Sortie anormale et plus rare

Sortie coupée

{"system": {"get_sysinfo": {"sw_ver": "1.0.6 Build 180627 Rel.081000 "," hw_ver ":" 1.0 "," model ":" HS300 (États-Unis) "," deviceId ":" 80067B24A755F99C4D6C1807455E09F91AB7B2AA "," oemId ":" 5C9E6254BEBAED63B2B610 "" 5C9E6254BEBAED63B2B610 "" 5C9E6254BEBAED63B2B610 "2966D24817" ": -59," longitude_i ": - 1222955," latitude_i ": 379078," alias ":" TP-LINK_Power Strip_4F01 "," mic_type ":" IOT.SMARTPLUGSWITCH "," feature ":" TIM: ENE "," mac ":" B0: BE: 76: 12: 4F: 01 "," mise à jour ": 0," led_off " : 0, "children": [{"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA00", "state": 0, "alias": "CezHeat", "on_time": 0, "next_action": {"type": - 1}} , {"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA01", "state": 1, "alias": "CezUVB", "on_time": 191207, "next_action": {"type": - 1}}, {"id": " 80067B24A755F99C4D6C1807455E09F91AB7B2AA02 "," state ": 1," alias ":" CyanHeat "," on_time ": 191207," next_action ": {" type ": - 1}}, {" id ":" 80067B24A7505F99C4D6C1 " 1, "alias": "ZanderHeat", "on_time": 191207, "next_action": {"type": - 1}}, {"id": "80067B24A755F99C4D6C1807455E09F91AB7B2AA04", "state": 1, "alias": " CairoHeat "," sur

Conclusion

Si quelqu'un pouvait me fournir une solution ou une explication sur les raisons pour lesquelles la sortie / le flux est coupé, ce serait très apprécié. J'ai utilisé une grande partie du code de ce module open source . Je cherche également à mieux comprendre comment tout cela fonctionne, donc si vous pouviez expliquer un peu plus, je l'apprécierais vraiment.


0 commentaires

3 Réponses :


3
votes

Selon la documentation , le bufsize L'argument spécifie uniquement la quantité maximale de données à lire:

socket.recv (bufsize [ flags]) Recevoir les données du socket. Le retour value est un objet bytes représentant les données reçues. Le maximum la quantité de données à recevoir à la fois est spécifiée par bufsize . Voir le Page de manuel Unix recv (2) pour la signification de l'argument optionnel drapeaux; il vaut zéro par défaut.

/ pre>

Un autre exemple qui pourrait mieux convenir à votre application pourrait être d'attendre une taille de message fixe (1221 comme indiqué par votre question):

def recv_message(connection):
    data = list()
    transferred_bytes= 0
    while transferred_bytes < 1221:
        data.append(connection.recv(min(1221-transferred_bytes, 2048)))
        if not data[-1]:
            raise RuntimeError("socket connection broken")
        transferred_bytes += len(data[-1])
    return b''.join(data)


4 commentaires

Comme indiqué par @SergeBallesta, il est préférable de rechercher le tramage dans le protocole de niveau supérieur (dans ce cas équilibré {et}) que de supposer que le message est de taille fixe.


Merci beaucoup! Ça marche; Cependant, il faut environ 30 secondes à chaque fois pour terminer la fonction entière (prend généralement une demi-seconde avec le code d'origine.) La prise intelligente elle-même est-elle responsable de la façon dont elle gère les connexions TCP?


Oui, le (premier) code attend que la prise «raccroche» avant de renvoyer les données. Si la prise ne raccroche pas, elle attendra plus de données.


@MarcoC Certaines recherches sur le protocole utilisé par votre plug semblent suggérer que les 4 premiers octets ( data [: 4] ) qui sont jetés contiennent en fait la longueur du message (moins les 4 octets pour la longueur ) donc ´struct.unpack ("> I", data [: 4]) [0] + 4´ doit être la longueur qui peut être utilisée avec le code 2



2
votes

Ceci n'est qu'un complément à la réponse de SimonF. La cause du problème est en effet que TCP est un protocole de flux, donc les paquets peuvent être fragmentés ou réassemblés à n'importe quel état: pile TCP / IP de l'expéditeur, équipements réseau, pile TCP / IP du récepteur - j'inclus la bibliothèque de la couche utilisateur dans le Pile TCP / IP ici pour simplifier.

C'est la raison pour laquelle, vous devriez toujours utiliser un protocole de niveau supérieur au-dessus de TCP pour pouvoir diviser le flux en messages sensibles . Ici, vous pouvez noter que la fin d'un message est '}}}' , vous pouvez donc concaténer l'entrée dans un tampon jusqu'à ce que vous trouviez ce modèle:

def recv_until(c, guard):
    """Receive data from a socket until guard if found on input"""
    guard_sz = len(guard) - 1
    data = b''
    sz = 0
    while True:
        buffer = c.recv(1024)      # read by chuncks of size 1024 (change value to your needs)
        got = len(buffer)
        data += buffer             # concatenate in buffer
        ix = data.find(guard, sz - guard_sz if sz > guard_sz else 0)    # is guard found?
        if ix != -1:
            return (data[:ix + guard_sz + 1],   # return the message, and what could be behind it
                data[ix + guard_sz + 1:])

        sz += got


1 commentaires

Mieux vaut compter {et} au fur et à mesure, et rechercher le final} qui correspond à l'ouverture d'équilibrage {(par opposition à supposer que l'emboîtement est toujours 3 profond et à supposer qu'il n'y a pas d'espace blanc entre les} finales) : l'analyse selon laquelle cela est causé par le fait que TCP est un protocole de flux est correcte; une lecture TCP n'est pas garantie de remplir le tampon et vous devez continuer à lire jusqu'à ce que vous ayez toutes les données dont vous avez besoin, comme déterminé par le cadrage à un niveau de protocole plus élevé)



0
votes

Marco, veuillez utiliser la méthode recv_into (buffer [ nbytes [ flags]]) pour le socket.

Mon exemple pour TCP-microserver:

import socket
import struct

def readReliably(s,n):
    buf = bytearray(n)
    view = memoryview(buf)
    sz = 0
    while sz < n:
        k = s.recv_into(view[sz:],n-sz)
        sz += k
    # print 'readReliably()',sz
    return sz,buf

def writeReliably(s,buf,n):
    sz = 0
    while sz < n:
        k = s.send(buf[sz:],n-sz)
        sz += k
    # obj = s.makefile(mode='w')
    # obj.flush()
    # print 'writeReliably()',sz
    return sz

# Client
host = "127.0.0.1"
port = 23456
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect((host,port))
# Request
buf = struct.pack("4B",*[0x01,0x02,0x03,0x04])
io.writeReliably(s,buf,4)
# Response
sz,buf = io.readReliably(s,4)
a = struct.unpack("4B",buf)
print repr(a)

# Server
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
#s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
s.bind((host,port))
s.listen(10) # unaccepted connections
while True:
  sk,skfrom = s.accept()
  sz,buf = io.readReliably(sk,4)
  a = struct.unpack("4B",buf)
  print repr(a)
  # ...
  io.writeReliably(sk,struct.pack("4B",*[0x01,0x02,0x03,0x04]))


1 commentaires

NameError: le nom 'io' n'est pas défini