讲师中心 微信公众号
AI工具推荐 视频效率加速

如何使用Python解析UDP传输的C语言嵌套结构体数组

陌丽君_1578

陌丽君_1578

发布时间:2025-11-20 11:31:15

|

517人浏览过

|

来源于php中文网

原创

如何使用python解析udp传输的c语言嵌套结构体数组

本教程旨在解决C语言嵌套结构体通过UDP传输到Python时,因指针序列化问题导致的解析困难。文章将深入探讨两种解决方案:一是利用`ctypes`模块进行分步解析和动态构建内部数组,二是采用纯Python类结合`struct`模块实现高效的数据反序列化,帮助开发者准确处理跨语言结构体数据。

1. 理解C语言结构体与指针的传输挑战

当C语言中包含指针的结构体通过网络(如UDP)传输时,直接使用memcpy复制整个结构体内存内容到缓冲区并发送,会遇到一个核心问题:只复制了指针变量本身的值(即内存地址),而没有复制指针所指向的实际数据。例如,一个MyStruct包含MyInnerStruct *InnerStruct字段,memcpy(&testStruct, buffer, sizeof(MyStruct))只会将testStruct在C程序内存中的地址值复制到缓冲区,而不是InnerStruct数组的实际内容。

在Python端,如果尝试使用ctypes将接收到的字节流直接解析为一个包含指针的结构体,那么该指针字段将包含一个在Python进程内存空间中无效的C程序内存地址。任何尝试解引用或访问该地址的操作都将失败或导致不可预测的行为。

因此,正确的做法是,C端在发送数据时,需要将主结构体的标量字段和内部数组的所有元素序列化成一个连续的字节流。Python端再根据这个序列化规则进行反序列化。

立即学习“Python免费学习笔记(深入)”;

2. Python ctypes分步解析与动态构建内部数组

这种方法利用ctypes来定义C结构体的Python对应物,并通过struct模块手动解析字节流,然后动态构建内部数组。

python学习
python学习

Python 编程学习助手 skill,面向零基础用户。 当用户表达以下意图时触发: - 学习 Python 基础概念(变量、数据类型、循环、函数、类等) - 请求 Python 练习题或测验 - 请求审查或调试 Python 代码 - 请求 Python 项目实战指导(爬虫、数据分析、Web 开发等入门项目) - 泛化表述如"教我 Python""我想学编程""Python 怎么写""帮我学 Python" 此 skill

下载

2.1 C端数据序列化(概念模拟)

假设C端已经将数据序列化为以下字节流格式: 主结构体字段1 (int) | 主结构体字段2 (float) | 内部结构体元素1 (int, float) | 内部结构体元素2 (int, float) | ...

以下Python代码模拟了C端发送这种序列化数据的方式:

import struct
import socket

# 模拟C端发送的数据:
# field1=4 (int), field2=3.5 (float)
# 接着是4个MyInnerStruct元素:
# (1, 1.25), (2, 2.5), (3, 2.75), (4, 3.00)
# '<if' 表示小端序,int和float
data = struct.pack('<ififififif', 4, 3.5, 1, 1.25, 2, 2.5, 3, 2.75, 4, 3.00)

# 模拟发送到UDP端口
with socket.socket(type=socket.SOCK_DGRAM) as s:
    s.sendto(data, ('localhost', 5000))
    print("模拟数据已发送。")

2.2 Python ctypes接收端实现

在Python端,我们需要定义ctypes.Structure来匹配C语言结构体的布局。

import socket
import struct
import ctypes as ct

# 定义内部结构体
class MyInnerStruct(ct.Structure):
    _fields_ = (('field4', ct.c_int),
                ('field5', ct.c_float))

    def __repr__(self):
        return f'({self.field4}, {self.field5})'

# 定义主结构体
class MyStruct(ct.Structure):
    _fields_ = (('field1', ct.c_int),
                ('field2', ct.c_float),
                ('field3', ct.POINTER(MyInnerStruct))) # 注意这里是POINTER

    def __repr__(self):
        # 访问field3时,需要确保它已被正确赋值为一个ctypes数组
        # 否则尝试list(self.field3[:self.field1])可能会失败
        inner_data = []
        if self.field3: # 检查指针是否有效
            for i in range(self.field1):
                inner_data.append(self.field3[i])
        return f'MyStruct(field1={self.field1}, field2={self.field2}, field3={inner_data})'

# UDP接收设置
sock = socket.socket(type=socket.SOCK_DGRAM)
sock.bind(('', 5000))
print("等待接收UDP数据...")

# 接收数据
data, addr = sock.recvfrom(40960) # 接收足够大的缓冲区

# 1. 解析主结构体的标量字段
# '<if' 表示小端序,一个int和一个float
field1, field2 = struct.unpack_from('<if', data)

# 2. 初始化MyStruct,此时field3(指针)尚未指向有效数据
received_struct = MyStruct(field1=field1, field2=field2)

# 3. 根据field1的值(数组长度),动态分配一个MyInnerStruct的ctypes数组
inner_array_type = MyInnerStruct * field1
inner_array = inner_array_type()

# 4. 计算内部数组数据在接收缓冲区中的起始位置和每个元素的大小
start_of_inner_data = struct.calcsize('<if') # 主结构体标量字段的大小
size_of_inner_element = struct.calcsize('<if') # MyInnerStruct元素的大小

# 5. 循环解析字节流中的内部结构体元素,并填充到动态数组中
current_index = start_of_inner_data
for i in range(field1):
    # 从当前位置开始解析一个MyInnerStruct元素
    field4, field5 = struct.unpack_from('<if', data[current_index:])
    inner_array[i] = MyInnerStruct(field4=field4, field5=field5)
    current_index += size_of_inner_element

# 6. 将动态分配并填充好的ctypes数组赋值给主结构体的指针字段
received_struct.field3 = inner_array

# 打印完整的接收结果
print("接收到的结构体:", received_struct)

sock.close()

2.3 注意事项

  • 字节序(Endianness):struct.pack和struct.unpack_from中的格式字符串(如'<if')指定了字节序。'<'表示小端序,'>'表示大端序。C端和Python端的字节序必须一致。
  • 结构体对齐:ctypes通常会尝试模拟C语言的默认结构体对齐规则。如果C代码使用了特定的#pragma pack或其他对齐指令,Python ctypes也需要通过_pack_ = N来指定相同的对齐方式,以确保内存布局一致。在本例中,由于只使用了基本类型,默认对齐通常不会导致问题。
  • 内存管理:ctypes动态创建的数组在Python中由Python的垃圾回收机制管理。当received_struct或inner_array不再被引用时,它们占用的内存会被释放。

3. 纯Python类结合struct模块进行反序列化

对于不需要将Python对象传回C语言或与C库进行复杂交互的场景,完全放弃ctypes,使用纯Python类结合struct模块进行数据解析,通常会更简洁、更“Pythonic”,且易于理解和维护。

3.1 纯Python类定义与解析

import socket
import struct

# 定义纯Python的内部结构体类
class MyInnerStruct:
    _format = '<if'  # 定义其序列化格式 (int, float)
    _size = struct.calcsize(_format) # 计算序列化后的大小

    def __init__(self, field4, field5):
        self.field4 = field4
        self.field5 = field5

    @classmethod
    def from_data(cls, data_buffer):
        """从字节缓冲区解析单个MyInnerStruct实例。"""
        # 使用unpack_from从缓冲区的开头解析
        return cls(*struct.unpack_from(cls._format, data_buffer))

    @classmethod
    def from_data_array(cls, data_buffer, count):
        """从字节缓冲区解析MyInnerStruct实例数组。"""
        inner_structs = []
        for n in range(count):
            # 每次从缓冲区中提取一个MyInnerStruct大小的切片进行解析
            start = n * cls._size
            end = (n + 1) * cls._size
            inner_structs.append(cls(*struct.unpack_from(cls._format, data_buffer[start:end])))
        return inner_structs

    def __repr__(self):
        return f'MyInnerStruct(field4={self.field4}, field5={self.field5})'

# 定义纯Python的主结构体类
class MyStruct:
    _format = '<if'  # 定义其序列化格式 (int, float)
    _size = struct.calcsize(_format) # 计算序列化后的大小

    def __init__(self, field1, field2, field3_array=None):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3_array if field3_array is not None else []

    @classmethod
    def from_data(cls, data_buffer):
        """从字节缓冲区解析MyStruct实例及其内部数组。"""
        # 1. 解析主结构体的标量字段
        field1, field2 = struct.unpack_from(cls._format, data_buffer)

        # 2. 解析内部数组(从主结构体字段之后开始)
        # data_buffer[cls._size:] 表示跳过主结构体字段,直接处理内部数组数据
        field3_array = MyInnerStruct.from_data_array(data_buffer[cls._size:], field1)

        # 3. 构建并返回MyStruct实例
        return cls(field1, field2, field3_array)

    def __repr__(self):
        return f'MyStruct(field1={self.field1}, field2={self.field2}, field3={self.field3})'

# UDP接收设置 (与ctypes示例相同)
sock = socket.socket(type=socket.SOCK_DGRAM)
sock.bind(('', 5000))
print("等待接收UDP数据...")

# 接收数据
data, addr = sock.recvfrom(40960)

# 使用MyStruct的类方法直接从接收到的数据中构建对象
received_struct = MyStruct.from_data(data)
print("接收到的结构体:", received_struct)

sock.close()

3.2 优点与适用场景

  • 简洁性:代码更简洁,避免了ctypes的复杂性(如指针、内存地址、类型映射等)。
  • Pythonic:更符合Python的编程习惯,直接操作Python对象。
  • 灵活性:数据解析逻辑完全由Python控制,可以更容易地处理更复杂或变化的数据格式。
  • 适用场景:当主要目的是将接收到的二进制数据转换为Python对象进行后续处理,而不需要与C语言进行直接内存交互时,此方法是更优的选择。

4. 总结与选择建议

处理C语言嵌套结构体通过UDP传输到Python的问题,关键在于理解C语言指针的本质以及数据的正确序列化与反序列化。

  1. C端发送:必须将所有相关数据(主结构体标量字段和所有内部数组元素)序列化成一个连续的字节流进行发送,而不是仅仅memcpy主结构体(如果它包含指针)。
  2. Python接收:
    • ctypes分步解析:适用于需要将Python对象传回C语言、与C库深度交互,或者需要精确模拟C语言内存布局的场景。它提供了对C类型和内存的细粒度控制,但代码相对复杂。
    • 纯Python类反序列化:适用于大多数仅仅需要解析数据并转换为Python对象进行后续处理的场景。它更简洁、更Pythonic,通常更容易开发和维护。

在选择哪种方法时,请根据你的具体需求权衡复杂性、性能和与C语言交互的深度。对于大多数数据解析任务,纯Python类结合struct模块的方法通常是更推荐的选择。无论哪种方法,务必确保C端和Python端的字节序和数据类型定义(包括对齐)保持一致。

热门AI工具

更多
Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3011

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

554

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

522

2026.02.12

css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

6667

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

764

2025.10.23

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1758

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2744

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6104

2023.10.24

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

0

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Uvicorn官方安装与入门手册
Uvicorn官方安装与入门手册

共0课时 | 0人学习

Python uv官方项目管理指南
Python uv官方项目管理指南

共0课时 | 0人学习

Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn