【问题标题】:Creating application level packets in Python在 Python 中创建应用程序级别的数据包
【发布时间】:2018-09-03 05:41:49
【问题描述】:

我有一个使用 python 3.6 运行的基本多线程客户端服务器

现在一旦建立连接,我想创建将通过 tcp/ip 发送的应用程序级数据包。这样做的目的是进行三次握手以识别多个客户端,然后对它们进行身份验证。该数据包还将用于向服务器发送某些有效负载。

由于 python 没有任何数据类型,例如结构,所以我很难创建这些数据包。我不能使用元组,因为它们是不可变的,我尝试使用 recordclass 并使用 c_type 中的结构。

使用记录类时数据发送不正确,因为我不知道每个数据包的确切大小,我将 recv() 参数保持在最大限制,但是如果数据包的长度较短,这会将客户端移动到阻塞状态然后是最大限制。 在使用 c_type 结构时,我可以发送数据,但它是以 \xbct\x00\x106\xe0\x02ff\xc8B 这样的格式接收的,我无法将其转换回原始形式。

我们将不胜感激任何形式的帮助。

编辑: 到目前为止,我已经做到了。我附在我正在使用的代码 sn-ps 下面,结构字段是任意的,我稍后会更改它们。

服务器端:

...
   ...
   class app_packet(Structure):
       _fields_ = [('packet_type',c_wchar_p),
                  ('sensor_name',c_wchar_p),
                  ('value',c_float)]

   syn=app_packet('syn','temperature',100.2)
   connectionSocket.sendall(syn)
   ...
   ...

客户端:

    ...
    ...
    class app_packet(Structure):
        _fields_ = [('packet_type',c_wchar_p),
                    ('sensor_name',c_wchar_p),
                    ('value',c_float)]

    data=clientSocket.recv(1024)
    syn=unpack('3s4sf',data)
    a=str(syn)
    print("unpacked="+a)
    ...
    ...

但问题仍然存在,即使在我解包收到的数据包后,字符串数据仍为字节格式,而浮点数据已正确转换。这就是我得到的 print 语句的输出

unpacked=  (b'(\xbcY', b'\x00\xa0\xa6\xc2', 100.19999694824219)

我尝试了不同的编码/解码方案,但到目前为止没有任何效果,我无法将其转换回来

【问题讨论】:

  • Python 确实有这样的数据类型——你在ctypes中找到了它们——但通常你想使用struct模块来反序列化一个更“正常”的值到字节流中,或构建(或下载)在此之上构建的更高级别的序列化库。如果您可以给我们minimal reproducible example,我可以为您展示如何为您的情况做这件事。
  • 此外,数据作为字节流接收,因为这就是您发送的内容,就像它们在 C 中一样。如果您想使用ctypes.Structure,您需要转换字节返回给它,就像你必须将结构转换为字节来发送它一样。
  • 还有一件事:如果您确实想围绕 Structure 而不是 struct 模块构建东西,那么至少在开发过程中通常很有帮助的一个快速技巧是用相同字节大小的 char 或 uint8 数组将结构包装在 Union 中的类装饰器。这使得两种表示之间的转换更具可读性——只需设置字节元素,然后从结构元素中读取属性,反之亦然。如果这听起来像你想要的东西,但你不知道如何写它,那么演示并不难。
  • 最后一件事——你需要这些是可变的吗?对于握手数据,我无法想象在构造(在发送端)或从字节转换(在接收端)之后要更改什么。如果您不想使用更高级别的东西,namedtuple 和基于struct 的用于在有线协议之间进行转换的方法似乎正是正确的方法。
  • @abarnert 首先感谢您的帮助。您能告诉我将字节转换回来的方法吗?有没有预定义的方法呢​​。我希望这些是可变的的原因是我实际上是在尝试创建一个包含我需要的所有字段的单个数据包,然后根据数据包类型我将设置相关字段,如果它们是不可变的,我不能做那。即使我确实为握手制作了另一个单独的数据包,我也需要在其中发送我的客户端 ID,这样最终会改变。

标签: python tcp-ip packets


【解决方案1】:

您的代码的主要问题是您实际上并没有发送字符串。

您已经定义了一个包含两个c_wchar_p 成员的结构,或者用C 术语来说,是wchar_t * 指针。您将这些指针发送过来,但从不发送它们指向的数据。那是不可能的。

您不能只发送包含任何语言指针的 C structs。您必须编写一些更高级别的协议,以某种方式包含实际字符串而不是指针,然后编写序列化和反序列化到struct 的代码。使用struct.pack 之类的函数比ctypes.Structure 更容易做到这一点。在像 netstrings 这样的更高级别的协议之上更容易实现,如果您只使用带有一些人类可读框架的基于文本的协议,例如使用换行符作为分隔符的换行符转义 JSON 文本,则更容易。


如果您真的想要一个基于将固定大小的结构转储到网络的二进制协议,那么您的结构必须是固定大小且自包含的。例如,如果您的 packet_type 始终最多 4 个字符,而您的 sensor_name 最多 30 个字符,并且可以为较短的名称浪费空间,您可以这样做:

class app_packet(Structure):
    _fields_ = [('packet_type',c_wchar*4),
                ('sensor_name',c_wchar*30),
                ('value',c_float)]

现在字符直接嵌入到结构中,所以它可以工作了。


除非它不会真正起作用,因为您的数据类型不是网络可移植的。 wchar_t 可以是 2 字节或 4 字节——不仅在不同平台之间,甚至在同一平台上使用不同编译器或标志构建的二进制文件之间。 (另外,它们当然是原生字节序。)如果您真的想要嵌入 2 字节或 4 字节字符串,您必须明确说明:使用 c_uint16c_uint32,使用 s.encode('utf-16') 或编码s.encode('utf-32'),然后是 memcpycast 和切片复制。但是当然,在您将它们拉出,将它们放回并解码它们之前,它们不是您代码中的字符串,此时您最好首先使用适当的协议。


此外,目前还不清楚为什么您首先希望将握手数据存储在这样的结构中。为什么不把它作为一个带有两个字符串和一个浮点数的元组(或namedtuple 或普通类)传递,并在它通过/从网络进入时进行序列化/反序列化。

您在评论中提到您需要它们是可变的,但这并不能解释它;你想要可变的握手数据就更没有意义了。此外,您可以轻松地用不同的字符串创建一个新元组,而不是让您可以就地变异的类似字符串的成员。

【讨论】:

  • 感谢您的宝贵时间和帮助!我理解我使用 c_wchar_p 所犯的错误。我已将代码更改为使用元组并使用 struct.pack/unpack 发送它,现在它可以正常工作了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-03
  • 1970-01-01
  • 2011-06-12
  • 1970-01-01
  • 2016-09-05
  • 2016-05-01
  • 1970-01-01
相关资源
最近更新 更多