0

0

高效处理C结构体数组与Python ctypes的网络通信

花韻仙語

花韻仙語

发布时间:2025-11-26 14:40:02

|

784人浏览过

|

来源于php中文网

原创

高效处理c结构体数组与python ctypes的网络通信

本文深入探讨了在C/C++与Python之间通过UDP协议传输包含指针的复杂结构体数组的挑战与解决方案。我们将详细讲解C端如何正确序列化包含内嵌结构体数组的数据,以及Python端如何使用`ctypes`或纯Python类高效地反序列化并访问这些数据。文章提供了两种Python接收方案:基于`ctypes`的内存映射方法和基于`struct`模块的纯Python对象构建方法,并强调了网络传输中数据序列化的关键注意事项。

在C/C++与Python之间进行数据交换,特别是涉及复杂结构体和动态数组时,需要精确地处理数据序列化和反序列化。当数据通过网络(如UDP)传输时,C语言中的指针(例如指向数组的指针)其值仅是发送方内存中的地址,无法直接在接收方机器上解析。因此,核心挑战在于如何将指针指向的实际数据内容一并序列化传输,并在Python端正确地重建数据结构。

1. C/C++端的数据序列化策略

原始的C代码尝试通过memcpy(&testStruct, ..., sizeof(MyStruct))发送结构体。然而,sizeof(MyStruct)只包含了MyStruct自身的成员(intValue, floatValue)以及InnerStruct指针变量的值(一个内存地址),而没有包含InnerStruct指针所指向的实际数组数据。为了使Python端能够正确解析,C端必须将所有相关数据扁平化为连续的字节流进行发送。

正确的C/C++序列化逻辑应遵循以下顺序:

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

  1. 发送 MyStruct 的直接成员(例如 intValue 和 floatValue)。
  2. 紧接着,根据 intValue(通常用作数组长度),循环发送 MyInnerStruct 数组中的每一个元素。

以下是一个概念性的C++发送端代码示例,展示了如何构造符合要求的字节流。请注意,这里使用了一个Python struct.pack 的等效模拟,以确保数据格式与Python接收端兼容。

#include <iostream>
#include <vector>
#include <winsock2.h> // For Windows sockets
#include <cstring>    // For memcpy

// 定义与Python ctypes匹配的结构体
struct MyInnerStruct {
    int intValue;
    float floatValue;
};

struct MyStruct {
    int intValue; // 实际在此用作内部数组的长度
    float floatValue;
    // 注意:MyInnerStruct *InnerStruct; 在网络传输时,指针本身不被发送
    // 而是发送它所指向的实际数据
};

int main() {
    // 假设要发送的数据
    MyStruct testStruct;
    testStruct.intValue = 4; // 内部数组有4个元素
    testStruct.floatValue = 3.5f;

    std::vector<MyInnerStruct> innerArray(testStruct.intValue);
    for (int i = 0; i < testStruct.intValue; ++i) {
        innerArray[i].intValue = i + 1;
        innerArray[i].floatValue = (float)(i + 1) + 0.25f * i;
    }

    // 计算需要发送的总字节数
    size_t totalSize = sizeof(testStruct.intValue) + sizeof(testStruct.floatValue) +
                       (testStruct.intValue * sizeof(MyInnerStruct));

    // 创建一个缓冲区来存储序列化后的数据
    std::vector<char> sendBuffer(totalSize);
    char* currentPtr = sendBuffer.data();

    // 1. 拷贝 MyStruct 的 intValue
    memcpy(currentPtr, &testStruct.intValue, sizeof(testStruct.intValue));
    currentPtr += sizeof(testStruct.intValue);

    // 2. 拷贝 MyStruct 的 floatValue
    memcpy(currentPtr, &testStruct.floatValue, sizeof(testStruct.floatValue));
    currentPtr += sizeof(testStruct.floatValue);

    // 3. 拷贝 MyInnerStruct 数组的每个元素
    for (int i = 0; i < testStruct.intValue; ++i) {
        memcpy(currentPtr, &innerArray[i].intValue, sizeof(innerArray[i].intValue));
        currentPtr += sizeof(innerArray[i].intValue);
        memcpy(currentPtr, &innerArray[i].floatValue, sizeof(innerArray[i].floatValue));
        currentPtr += sizeof(innerArray[i].floatValue);
    }

    // --- 以下是UDP发送部分(与原问题中的C代码类似) ---
    // 为了简化,这里只展示了数据准备,实际发送需要完整的Winsock初始化和发送代码
    // ... (Winsock初始化、socket创建、目标地址设置等) ...
    // sendto(udpSocket, sendBuffer.data(), totalSize, 0, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr));
    // ... (错误处理、清理) ...
    std::cout << "C++ side: Data prepared for sending." << std::endl;
    // 打印模拟的Python struct.pack 输出
    std::cout << "Simulated Python struct.pack format: <ififififif" << std::endl;
    std::cout << "Values: " << testStruct.intValue << ", " << testStruct.floatValue;
    for (const auto& inner : innerArray) {
        std::cout << ", " << inner.intValue << ", " << inner.floatValue;
    }
    std::cout << std::endl;

    return 0;
}

注意: 在实际的跨平台通信中,还需要考虑字节序(endianness)问题。C++代码通常默认使用主机字节序,而Python struct 模块允许指定字节序(例如 < 表示小端,> 表示大端)。确保两端使用相同的字节序至关重要。

PPT.AI
PPT.AI

AI PPT制作工具

下载

为了方便测试,我们可以使用Python模拟一个发送端,它会生成与上述C++逻辑相同的字节流:

import struct
import socket

# 模拟发送的数据
# field1 (int), field2 (float)
# 接着是 field1 个 MyInnerStruct 元素,每个包含 field4 (int), field5 (float)
# 示例:field1=4, field2=3.5
# InnerStructs: (1, 1.25), (2, 2.5), (3, 2.75), (4, 3.0)
data_to_send = 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_to_send, ('127.0.0.1', 12345)) # 发送到本地的12345端口
    print("Python sender: Data sent successfully.")

2. Python端使用 ctypes 反序列化

ctypes 模块是Python与C语言库交互的强大工具,它可以定义与C结构体对应的Python类,并直接操作内存。然而,当接收到网络数据时,ctypes 的 POINTER 类型并不能自动将字节流中的数据解析为指向有效内存区域的指针。我们需要手动解析字节流,然后将解析出的数据填充到 ctypes 对象中。

import socket
import struct
import ctypes as ct

# 定义与C结构体对应的ctypes结构体
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))) # field3 是指向 MyInnerStruct 数组的指针

    def __repr__(self):
        # 确保在访问 field3 之前它已经被正确初始化
        if self.field3:
            # 使用 self.field1 作为数组长度来切片指针
            return f'[{self.field1}, {self.field2}, {list(self.field3[:self.field1])}]'
        else:
            return f'[{self.field1}, {self.field2}, <uninitialized_inner_array>]'

# UDP接收设置
HOST = '127.0.0.1'
PORT = 12345

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))
print(f"Python ctypes receiver: Listening on {HOST}:{PORT}")

try:
    data, addr = sock.recvfrom(40960) # 接收数据,缓冲区大小足够大
    print(f"Received {len(data)} bytes from {addr}")

    # 1. 首先解析 MyStruct 的直接字段 (field1, field2)
    # '<if' 表示小端序的 int 和 float
    field1, field2 = struct.unpack_from('<if', data)

    # 创建 MyStruct 实例,此时 field3 (指针) 尚未初始化
    received_struct = MyStruct(field1, field2)

    # 2. 根据 field1 (数组长度) 分配 MyInnerStruct 数组的 ctypes 内存
    inner_array = (MyInnerStruct * field1)() # 创建一个包含 field1 个 MyInnerStruct 的 ctypes 数组

    # 3. 计算 MyStruct 直接字段的字节大小,以确定内部数组数据开始的位置
    start_of_inner_data = struct.calcsize('<if')
    # 计算每个 MyInnerStruct 元素的字节大小
    size_of_single_inner_element = struct.calcsize('<if')

    # 4. 遍历数据流,填充 inner_array
    current_index_in_data = start_of_inner_data
    for i in range(field1):
        # 从当前位置开始解包一个 MyInnerStruct 的字段
        field4, field5 = struct.unpack_from('<if', data, current_index_in_data)
        # 将解包出的值赋给 ctypes 数组的当前元素
        inner_array[i] = MyInnerStruct(field4, field5)
        # 移动到下一个 MyInnerStruct 数据的起始位置
        current_index_in_data += size_of_single_inner_element

    # 5. 将填充好的 ctypes 数组的地址赋给 received_struct.field3
    # ctypes 数组可以直接作为 POINTER 类型的值赋给指针字段
    received_struct.field3 = inner_array

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

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    sock.close()
    print("Socket closed.")

运行上述Python接收器,然后运行Python发送器,将得到如下输出:

Python ctypes receiver: Listening on 127.0.0.1:12345
Received 40 bytes from ('127.0.0.1', 5000) # 示例端口可能不同
Received Struct (ctypes): [4, 3.5, [(1, 1.25), (2, 2.5), (3, 2.75), (4, 3.0)]]
Socket closed.

3. 替代方案:纯Python类反序列化

对于不需要与C库进行内存级别交互,仅需将网络字节流转换为Python对象的场景,使用纯Python类结合 struct 模块进行反序列化可能更简单、更高效。这种方法避免了 ctypes 的一些复杂性,直接构建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, offset=0):
        """从字节缓冲区中解析单个 MyInnerStruct 实例"""
        f4, f5 = struct.unpack_from(cls._format, data_buffer, offset)
        return cls(f4, f5)

    @classmethod
    def from_data_array(cls, data_buffer, count, offset=0):
        """从字节缓冲区中解析 MyInnerStruct 实例数组"""
        inner_structs = []
        current_offset = offset
        for _ in range(count):
            inner_structs.append(cls.from_data(data_buffer, current_offset))
            current_offset += cls._size
        return inner_structs

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

class MyStruct:
    _format = '<if' # 小端序的 int 和 float (MyStruct 自身的字段)
    _size = struct.calcsize(_format)

    def __init__(self, field1, field2, field3_array=None):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3_array # field3 直接存储 MyInnerStruct 对象的列表

    @classmethod
    def from_data(cls, data_buffer):
        """从字节缓冲区中解析 MyStruct 实例及其内嵌数组"""
        # 1. 解析 MyStruct 的直接字段
        field1, field2 = struct.unpack_from(cls._format, data_buffer)

        # 2. 从 MyStruct 字段之后的数据开始解析 MyInnerStruct 数组
        # field1 用作数组长度
        inner_array_data_offset = cls._size
        field3_array = MyInnerStruct.from_data_array(data_buffer, field1, inner_array_data_offset)

        return cls(field1, field2, field3_array)

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

# UDP接收设置
HOST = '127.0.0.1'
PORT = 12345

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((HOST, PORT))
print(f"Python pure class receiver: Listening on {HOST}:{PORT}")

try:
    data, addr = sock.recvfrom(40960) # 接收数据
    print(f"Received {len(data)} bytes from {addr}")

    # 使用 MyStruct 的 from_data 类方法直接解析整个字节流
    received_struct = MyStruct.from_data(data)
    print("Received Struct (Pure Python):", received_struct)

except Exception as e:
    print(f"An error occurred: {e}")
finally:
    sock.close()
    print("Socket closed.")

运行此Python接收器(确保Python发送器已运行),将得到与 `ct

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

410

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

638

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

362

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

263

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

630

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

562

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

671

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

618

2023.09.22

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

25

2026.03.13

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号