Python:Socket发送结构体(以C结构体为例)

1 投票
1 回答
13392 浏览
提问于 2025-04-17 14:17

下面是C语言中的结构体,我正在尝试将其转换为Python,并使用Socket发送这个结构体。

C语言代码:

        struct CommandReq
        {
            char    sMark[6];  //start flag\r\n*KW
            short   nPackLen;  //packet length
            short   nFlag;     //command ID 0x0002
            int     nGisIp;    //GIS port
            short   nPort;     //GIS Port
            char    sData[50]; //command string
            char    sEnd[2];   //end flag "\r\n"
        };
        //source code
        CommandReq stResq;
        memset(&stResq, 0, sizeof(stResq));
        sprintf(stResq.sMark, "\r\n%s", "*KW");
        stResq.nFlag = 0x0002;
        stResq.nPackLen = sizeof(stResq);
        stResq.nGisIp = 0;
        stResq.nPort = 0;
        strcpy(stResq.sData, "*KW,CC09C00001,015,080756,#");
        strncpy(stResq.sEnd, "\r\n", 2);

我已经使用namedtuple在Python中创建了结构体,并用socket发送这个结构体,但不幸的是失败了。

Python代码:

from collections import namedtuple
MyStruct = namedtuple("MyStruct", "sMark nPackLen nFlag nGisIp nPort sData sEnd")

编辑,回复下面的回答

经过研究,发现Python 3.x中必须添加字符串的 .encode("ascii")。

      format_ = "6shhih50s2s"
      MyStruct = namedtuple("MyStruct", "sMark nPackLen nFlag nGisIp nPort sData sEnd")
      tuple_to_send = MyStruct(sMark="\r\n{}".format("*KW").encode("ascii"), 
                 nPackLen=struct.calcsize(format_),
                 nFlag=0x0002,
                 nGisIp=0,
                 nPort=0,
                 sData= "*KW,NR09G05133,015,080756,#".encode("ascii"),
                 sEnd="\r\n".encode("ascii"))
      string_to_send = struct.pack(format_, *tuple_to_send._asdict().values()
      socket.sendto(string_to_send, self.client_address)

附加问题

下面是发送的结构体数据包。

format_ = "6shhih50s2s" //total bytes should be 68 bytes?
    0d0a2a4b5700 //6 char \r\n*KW
    4600 // packet length 70 bytes
    0200 //command ID 0x0002,short 2 bytes
    00000000 //GIS port is integer 4 bytes
    0000 //GIS Port short 2 bytes
    0000 //unknown??what is this?
    2a4b572c4e5230394730353133332c3031352c3038303735362c230000000000000000000000000000000000000000000000 //sData[50] char 50 bytes
    0d0a //sEnd 2 char,2 bytes

为什么会有两个未知的字节呢?

1 个回答

2

namedtuple 并不完全等同于 C 语言中的结构体。如果你习惯使用结构体,可以看看 struct模块,它可以把结构化的信息转换成字符串。

一般来说,Python 的爱好者更喜欢使用 pickle模块 来进行序列化,也就是把数据转换成可以存储或传输的格式。

from collections import namedtuple
import pickle # or cPickle, it's faster
MyStruct = namedtuple("MyStruct", "sMark nPackLen nFlag nGisIp nPort sData sEnd")

tuple_to_send = MyStruct(sMark="abcdef", nPackLen=...)
string_to_send = pickle.dumps(tuple_to_send)

pickle 有两种版本,picklecPickle。后者速度更快,但只在 CPython 中可用(大多数程序员使用的版本),而 pickle 也可以在 Jython、IronPython 等其他版本中使用。

如果你想继续使用结构体(比如因为对方需要这种格式),你的格式字符串将是

format_ = "6shhih50s2s"

所以你可以这样做:

import struct
from collections import namedtuple

format_ = "6shhih50s2s"

MyStruct = namedtuple("MyStruct", "sMark nPackLen nFlag nGisIp nPort sData sEnd")
tuple_to_send = MyStruct(sMark="\r\n{}".format("*KW"), 
                     nPackLen=struct.calcsize(format_),
                     nFlag=0x0002,
                     nGisIp=0,
                     nPort=0,
                     sData= "*KW,NR09G05133,015,080756,#",
                     sEnd="\r\n")


string_to_send = struct.pack(format_, *tuple_to_send._asdict().values())

顺便说一下:namedtuple 是不可变的,也就是说,你不能这样做

tuple_to_send.sMark = "bcdefg"

来改变某个属性。如果你需要这样的功能,你必须创建一个类。

编辑

在 Python 3 中,字符串的处理方式发生了变化。需要进行从 Unicode 到字节的转换,例如:

import struct
from collections import namedtuple

format_ = "6shhih50s2s"

MyStruct = namedtuple("MyStruct", "sMark nPackLen nFlag nGisIp nPort sData sEnd")
tuple_to_send = MyStruct(sMark="\r\n{}".format("*KW").encode("ascii"), 
                     nPackLen=struct.calcsize(format_),
                     nFlag=0x0002,
                     nGisIp=0,
                     nPort=0,
                     sData= "*KW,NR09G05133,015,080756,#".encode("ascii"),
                     sEnd=b"\r\n")


string_to_send = struct.pack(format_, *tuple_to_send._asdict().values())

撰写回答