【问题标题】:Unable to parse Ctype structure in python received from socket无法解析从套接字接收的python中的Ctype结构
【发布时间】:2018-06-28 15:39:30
【问题描述】:

我有一个连接到嵌入式服务器的 python 客户端,我们使用 c 结构进行通信,能够将结构发送到服务器并且他能够接收和解析我的结构,套接字响应再次是我的结构无法解析,结构格式没有变化。

from ctypes import *

class CommonMessage(Structure):
        _pack_ = 1
        _fields_ = [
            ("sof", c_uint), ("request_id", c_uint),
            ("interface", c_uint), ("msg_type", c_uint),
            ("response", c_uint),
            ("data_len", c_int), 
            ("data", c_ubyte * msg_length)
        ]

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('192.168.98.64', 7000)
sock.connect(server_address)
sock.sendall(message_proto)
sz = sizeof(message_proto)
data = sock.recv(sz)
print(data)
b'\xcc\xdd\xee\xff~Y\xd4\x0b\x02\xb9\xa9\x00i\x02\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
parsed_data = CommonMessage.from_buffer_copy(data)

在尝试解析结构数据时出错

缓冲区太小(24 字节而不是至少 93 字节)

请帮忙....

【问题讨论】:

  • msg_length 定义在哪里?
  • TCP 是面向字节的。在解析之前,您必须验证您是否收到了至少 93 个字节(在这种情况下)。最好设计一个协议来发送数据的大小,然后发送数据...缓冲接收到的数据并仅提取完整的消息。
  • 消息长度是字符串的长度 msg_length = len(serialized_string)

标签: python python-3.x sockets struct ctypes


【解决方案1】:

CTypes 文档页面开始:[Python.Docs]: ctypes - A foreign function library for Python

这里存在设计问题。想象一下下面的场景:对方发送header + data(2次)like:

  1. 15 个字节 (data_len* = 15),以及对应于 struct 的字节之后的 15 个额外字节
  2. 25 个字节 (data_len = 25),以及对应于 struct 的字节之后的 25 个额外字节

您无法使用静态定义CommonMessage对该场景进行建模。您需要做的就是将其拆分为 2 个(headerdata)部分,并且每次您想要收到消息时:

  1. 阅读标题
  2. 在标题中查找 data_len
  3. 准确data_len字节作为数据读取

这是通用解决方案,适用于所有此类情况。如果您的情况有特殊性,则可以使用其他方法——要么更简单(来自代码 PoV)要么更快——但这些方法只适用于那些特殊性。但是由于这个问题没有提到任何这样的特殊性(它甚至不是一个 MCVE - 由于代码无法编译OOTB),我将坚持使用通用解决方案。
注意和[SO]: multiple send from server to client python (@CristiFati's answer)一样。

code00.py

import sys
import ctypes


#              b"\xcc\xdd\xee\xff~Y\xd4\x0b\x02\xb9\xa9\x00i\x02\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"  # Original buffer
INPUT_BUFFER = b"\xcc\xdd\xee\xff~Y\xd4\x0b\x02\xb9\xa9\x00i\x02\x00\x00\x01\x00\x00\x00\x0F\x00\x00\x00\x01\x02\x03\x04\x05Dummy TextIGNORED PART"  # At the end, there are bytes for data
#                                                                                         ^ - Modified data length to 15


class CommonMessageHeader(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("sof", ctypes.c_uint),
        ("request_id", ctypes.c_uint),
        ("interface", ctypes.c_uint),
        ("msg_type", ctypes.c_uint),
        ("response", ctypes.c_uint),
        ("data_len", ctypes.c_int),
    ]


def print_ctypes_instance_info(obj, indent="", metadata="", max_array_items=10, indent_increment="    "):
    if metadata:
        print("{:s}{:s}: {:}".format(indent, metadata, obj))
    else:
        print("{:s}[{:}]: {:}".format(indent, type(obj), obj))
    if isinstance(obj, ctypes.Structure):

        for field_name, field_type in obj._fields_:
            print_ctypes_instance_info(getattr(obj, field_name),indent=indent + indent_increment, metadata="[{:}] {:s}".format(field_type, field_name))
    elif isinstance(obj, ctypes.Array):

        if max_array_items < len(obj):
            items_len, extra_line = max_array_items, True
        else:
            items_len, extra_line = len(obj), False
        for idx in range(items_len):
            print_ctypes_instance_info(obj[idx], indent=indent + indent_increment, metadata="[{:d}]".format(idx))
        if extra_line:
            print("{:s}...".format(indent + indent_increment))


def get_instance_from_buffer(buffer):
    header_len = ctypes.sizeof(CommonMessageHeader)
    if header_len > len(buffer):
        raise ValueError(-1, "Buffer smaller than header")
    header = CommonMessageHeader.from_buffer_copy(buffer)
    required_buf_len = header_len + header.data_len
    if required_buf_len > len(buffer):
        raise ValueError(-2, "Buffer smaller than header and data")

    class CommonMessage(ctypes.Structure):
        _pack_ = 1
        _fields_ = [
            ("header", CommonMessageHeader),
            ("data", ctypes.c_ubyte * header.data_len)
        ]

    return CommonMessage.from_buffer_copy(buffer), required_buf_len


def main():
    print("sizeof(CommonMessageHeader): {:d}".format(ctypes.sizeof(CommonMessageHeader)))
    print("Input buffer len: {:d}\n".format(len(INPUT_BUFFER)))
    inst, inst_len = get_instance_from_buffer(INPUT_BUFFER)
    print_ctypes_instance_info(inst)
    print("\nOutput data as a string ({:d}): [{:s}]".format(len(inst.data), "".join([repr(chr(item))[1:-1] for item in inst.data])))
    print("The rest of the buffer: [{:}]".format(INPUT_BUFFER[inst_len:]))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

注意事项

  • 最大的函数(print_ctypes_instance_info)只是用来打印一个对象
  • 我对您的原始缓冲区进行了一些修改,以便真正可以使用
  • get_instance_from_buffer 是尝试将现有缓冲区转换为 CommonMessage 的函数(与问题中现在的 CommonMessageHeader 不同)实例:
    • 它需要一个缓冲区(意味着它已经被读取)。那是因为我不想创建整个服务器/客户端应用程序并交换消息。实际上我摆脱了所有与套接字相关的代码(因为我试图说明的协议并不真正依赖它)。
      可能最终的实现应该依赖于套接字而不是缓冲区,并且只从中读取所需数量的数据
    • CommonMessage 类是为每条消息动态创建的(它不像 CommonMessageHeader 那样是全局的),因为 ctypes.Structure._fields_ 只能设置一次
    • 如果缓冲区太小,会引发ValueError
    • 返回的 2nd 值是此实例“消耗”的字节数;例如,如果输入缓冲区较长,则剩余的可能属于下一条消息

输出

(py35x64_test) e:\Work\Dev\StackOverflow\q051086829>"e:\Work\Dev\VEnvs\py_pc064_03.0.04_test0\Scripts\python.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

sizeof(CommonMessageHeader): 24
Input buffer len: 51

[<class '__main__.get_instance_from_buffer.<locals>.CommonMessage'>]: <__main__.get_instance_from_buffer.<locals>.CommonMessage object at 0x000002106F1BBBC8>
    [<class '__main__.CommonMessageHeader'>] header: <__main__.CommonMessageHeader object at 0x000002106F1BBB48>
        [<class 'ctypes.c_ulong'>] sof: 4293844428
        [<class 'ctypes.c_ulong'>] request_id: 198465918
        [<class 'ctypes.c_ulong'>] interface: 11122946
        [<class 'ctypes.c_ulong'>] msg_type: 617
        [<class 'ctypes.c_ulong'>] response: 1
        [<class 'ctypes.c_long'>] data_len: 15
    [<class '__main__.c_ubyte_Array_15'>] data: <__main__.c_ubyte_Array_15 object at 0x000002106F1BBB48>
        [0]: 1
        [1]: 2
        [2]: 3
        [3]: 4
        [4]: 5
        [5]: 68
        [6]: 117
        [7]: 109
        [8]: 109
        [9]: 121
        ...

Output data as a string (15): [\x01\x02\x03\x04\x05Dummy Text]
The rest of the buffer: [b'IGNORED PART']

【讨论】:

  • 这回答了你的问题吗?
猜你喜欢
  • 2016-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-06
  • 2021-10-14
  • 1970-01-01
  • 2014-10-27
  • 2012-07-17
相关资源
最近更新 更多