Модуль сокета Python: ответ на данные Recv () обрезан - PullRequest
0 голосов
/ 08 января 2019

Объяснение

В настоящее время я пытаюсь управлять умным удлинителем с помощью скрипта Python. Для этого я использую соединение TCP с модулем сокета. Примерно в 75% случаев я получаю ответ / данные, которые я искал, и все работает отлично. Тем не менее, примерно в 25% случаев ответ обрезается на той же длине, 1024 байта. Это не имеет никакого смысла для меня, поскольку мой размер буфера на самом деле установлен в 2048 байт. Скорость, с которой я жду между использованием recv (), похоже, тоже не влияет / не вызывает это. Хотя TCP является потоком байтов, все еще возможно, что это может быть связано с фрагментацией пакета?

Код

Основной код

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

Функция шифрования

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

Функция дешифрования

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

выход

Сама строка, которую я получаю. Это, скорее всего, не актуально, но я подумал, что все равно включу. Это значение переменной rec.

Требуемый и обычный выход

Полный желаемый вывод

{"system": {"get_sysinfo": {"sw_ver": "1.0.6 Build 180627 Rel.081000" , "hw_ver": "1,0", "модель": "HS300 (США)", "DeviceId": "80067B24A755F99C4D6C1807455E09F91AB7B2AA", "oemId": "5C9E6254BEBAED63B2B6102966D24C17", "HWID": "34C41AA028022D0CCEA5E678E8547C54", "RSSI ": -60," longitude_i ": - 1222955" latitude_i ": 379078," псевдоним ":" TP-LINK_Power Strip_4F01" , "mic_type": "IOT.SMARTPLUGSWITCH", "особенность": "ТИМ: В", "макинтош": "B0: BE: 76: 12: 4F: 01", "обновление": 0, "led_off" : 0, "дети": [{ "ID": "80067B24A755F99C4D6C1807455E09F91AB7B2AA00", "состояние": 0, "Alias": "CezHeat", "on_time": 0, "next_action": { "тип": - 1}} , { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA01", "состояние": 1, "псевдоним": "CezUVB", "on_time": 191208, "next_action": { "тип": - 1}}, { "идентификатор":» 80067B24A755F99C4D6C1807455E09F91AB7B2AA02" , "состояние": 1, "псевдоним": "CyanHeat", "on_time": 191208, "next_action": { "тип": - 1}}, { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA03", "состояние": 1, "псевдоним": "ZanderHeat", "on_time": 191208, "next_action": { "тип": - 1}}, { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA04", "состояние": 1, "псевдоним":» CairoHeat», "on_time": 191208, "next_action": { "тип": - 1}}, { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA05", "состояние": 1, "псевдоним": "KodaMister", "on_time": 191208, "next_action": { "тип": - 1}}], "child_num": 6, "err_code": 0}}} * * тысяча тридцать девять

Ненормальный и более редкий выход

Отрезать вывод

{"system": {"get_sysinfo": {"sw_ver": "1.0.6 Build 180627 Rel.081000" , "hw_ver": "1,0", "модель": "HS300 (США)", "DeviceId": "80067B24A755F99C4D6C1807455E09F91AB7B2AA", "oemId": "5C9E6254BEBAED63B2B6102966D24C17", "HWID": "34C41AA028022D0CCEA5E678E8547C54", "RSSI ": -59," longitude_i ": - 1222955" latitude_i ": 379078," псевдоним ":" TP-LINK_Power Strip_4F01" , "mic_type": "IOT.SMARTPLUGSWITCH", "особенность": "ТИМ: В", "макинтош": "B0: BE: 76: 12: 4F: 01", "обновление": 0, "led_off" : 0, "дети": [{ "ID": "80067B24A755F99C4D6C1807455E09F91AB7B2AA00", "состояние": 0, "Alias": "CezHeat", "on_time": 0, "next_action": { "тип": - 1}} , { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA01", "состояние": 1, "псевдоним": "CezUVB", "on_time": 191207, "next_action": { "тип": - 1}}, { "идентификатор":» 80067B24A755F99C4D6C1807455E09F91AB7B2AA02" , "состояние": 1, "псевдоним": "CyanHeat", "on_time": 191207, "next_action": { "тип": - 1}}, { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA03", "состояние": 1, "псевдоним": "ZanderHeat", "on_time": 191207, "next_action": { "тип": - 1}}, { "идентификатор": "80067B24A755F99C4D6C1807455E09F91AB7B2AA04", "состояние": 1, "псевдоним":» CairoHeat "" на

Заключение

Если бы кто-нибудь мог предоставить мне решение или объяснение, почему вывод / поток отключается, это было бы очень ценно. Я использовал много кода из этого открытого модуля . Я также хочу понять, как все это работает, поэтому, если бы вы могли объяснить немного больше, я был бы очень признателен.

Ответы [ 3 ]

0 голосов
/ 08 января 2019

Это только дополнение к ответу SimonF. Причина проблемы заключается в том, что TCP является потоковым протоколом, поэтому пакеты могут быть фрагментированы или повторно собраны в любом состоянии: стек TCP / IP отправителя, сетевое оборудование, стек TCP / IP получателя - я включаю библиотеку пользовательского уровня в Стек TCP / IP здесь для упрощения.

Именно поэтому вы всегда должны использовать протокол более высокого уровня, чем TCP, чтобы разделить поток на разумные сообщения. Здесь вы можете заметить, что конец сообщения равен '}}}', поэтому вы можете объединять входные данные в буфере, пока не найдете этот шаблон:

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

Хитрость заключается в рассмотрении байта guard_sz из последнего фрагмента, в случае, когда охрана может быть разделена на два фрагмента.

0 голосов
/ 01 апреля 2019

Марко, пожалуйста, используйте recv_into(buffer[, nbytes[, flags]]) метод для сокета.

Мой пример для TCP-микросервера:

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]))
0 голосов
/ 08 января 2019

Согласно документации , аргумент bufsize указывает только максимальный объем данных для чтения:

socket.recv (bufsize [, flags]) Получать данные из сокета. Возврат значение - это байтовый объект, представляющий полученные данные. Максимум количество данных, которые должны быть получены за один раз, определяется значением bufsize . Увидеть Страница руководства Unix recv (2) для значения необязательного аргумента флаги; по умолчанию он равен нулю.

Чтобы обеспечить полную передачу данных, можно использовать такую ​​функцию, которая ожидает окончания соединения с сокетом (обозначается пустой строкой, возвращаемой из recv):

def recv_all(connection):
    """
    Function for all data

    :param connection: socket connection
    :return: received data
    """
    data = list()
    while True:
        data.append(connection.recv(2048))
        if not data[-1]:
            return b''.join(data)

Другим примером, который лучше подходит вашему приложению, может быть ожидание фиксированного размера сообщения (1221, как указано вашим вопросом):

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...