使用Python和Ctype结构通过套接字发送/接收数据

0 投票
2 回答
6387 浏览
提问于 2025-04-19 11:26

我正在尝试在一个Python客户端和服务器之间发送和接收C语言的结构体数据,反之亦然。客户端和服务器之间的连接和数据交换都很顺利。客户端向服务器发送一个ctype结构体,服务器又把它发回去。问题是,我不知道如何在客户端解析收到的消息,并提取出结构体中的数据。我的最终想法是让一个Python服务器和一个C语言客户端相互交流,并按照预定义的结构交换数据。

以下是我在Python中编写的客户端和服务器的代码:

客户端代码

import socket
import sys
import time

from ctypes import *

class payload_t(Structure):
    _fields_ = [("ms", c_ulong),
                ("counter", c_ulong),
                ("DHT_temperature", c_float),
                ("DHT_humidity", c_float),
                ("DS_temperature", c_float),
                ("temperature_setpoint", c_float),
                ("time_setpoint", c_float)]

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect the socket to the port where the server is listening
server_address = ('localhost', 10000)
print >>sys.stderr, 'connecting to %s port %s' % server_address
sock.connect(server_address)

try:
    for i in range(0,10):
        # Send data
        payload=payload_t(i*1000,i+1,25.2,45.7,25.8,33.22,3600.0)
        message = payload
        # 'This is the message.  It will be repeated.'
        print 'length of message %d' % sizeof(message)
        print 'sending "', message.ms, message.counter, message.DHT_temperature, message.DHT_humidity, message.DS_temperature, message.temperature_setpoint, message.time_setpoint, '"'
        sock.sendall(message)
        # time.sleep(0.1)
        # Look for the response
        amount_received = 0
        amount_expected = sizeof(message)

        while amount_received < amount_expected:
            datap = sock.recv(sizeof(message))
            amount_received += len(datap)
        print >>sys.stderr, 'received "%s"' % datap
        print type(datap)
        payload=payload_t()
        datap.readinto(payload)
        data=datap.readinto(payload_t)
        data=struct.unpack(payload_t,datap)
            print 'Received "', data.ms, data.counter, data.DHT_temperature, data.DHT_humidity, data.DS_temperature, data.temperature_setpoint, data.time_setpoint, '"'

finally:
    print >>sys.stderr, 'closing socket'
    sock.close()

服务器代码

import socket
import sys
from ctypes import *

class payload_t(Structure):
    _fields_ = [("ms", c_ulong),
                ("counter", c_ulong),
                ("DHT_temperature", c_float),
                ("DHT_humidity", c_float),
                ("DS_temperature", c_float),
                ("temperature_setpoint", c_float),
                ("time_setpoint", c_float)]

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 10000)
print >>sys.stderr, 'starting up on %s port %s' % server_address
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)
payload=payload_t(0,0,0.0,0.0,0.0,0.0,0.0)
while True:
    # Wait for a connection
    print >>sys.stderr, 'waiting for a connection'
    connection, client_address = sock.accept()

    try:
        print >>sys.stderr, 'connection from', client_address

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv(sizeof(payload))
            print >>sys.stderr, 'received "%s"' % data
            if data:
                print >>sys.stderr, 'sending data back to the client'
                connection.sendall(data)
            else:
                print >>sys.stderr, 'no more data from', client_address
                break

    finally:
        # Clean up the connection
        connection.close()

2 个回答

-1

关于直接用ctypes.Structure的实例来调用recv_into,你觉得怎么样?

这是我修改过的服务器代码,希望没有删掉你想保留的功能。

import socket
import sys
from ctypes import *

class payload_t(Structure):
    _fields_ = [("ms", c_ulong),
                ("counter", c_ulong),
                ("DHT_temperature", c_float),
                ("DHT_humidity", c_float),
                ("DS_temperature", c_float),
                ("temperature_setpoint", c_float),
                ("time_setpoint", c_float)]

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the port
server_address = ('localhost', 10000)
print >>sys.stderr, 'starting up on %s port %s' % server_address
sock.bind(server_address)

# Listen for incoming connections
sock.listen(1)
payload=payload_t()
while True:
    # Wait for a connection
    print >>sys.stderr, 'waiting for a connection'
    connection, client_address = sock.accept()

    try:
        print >>sys.stderr, 'connection from', client_address

        # Receive the data in small chunks and retransmit it
        while True:
            data = connection.recv_into(payload)
            print >>sys.stderr, 'received "%s"' % data
            if data:
                print >>sys.stderr, 'sending data back to the client'
                print >>sys.stderr, 'time_setpoint "%f"' % payload.time_setpoint
                connection.sendall(payload)
            else:
                print >>sys.stderr, 'no more data from', client_address
                break

    finally:
        # Clean up the connection
        connection.close()

基本上,我做的唯一改变就是直接用socket.recv_into来填充你的结构体实例:https://docs.python.org/3/library/socket.html#socket.recv_into

这样一来,我就可以直接访问这个结构体里的属性了。

我猜这个结构体的大小还算小(28个字节),所以数据可以在一次recv_into调用中全部接收。如果你需要多次调用recv_into才能完整构建这个结构体,那就...

如果你用的是Python 3.7,你可以通过切片内存视图来多次调用你的结构体:

mv = memoryview(payload).cast('B')
size = sizeof(payload)
while size > 0:
    data = connection.recv_into(mv) 
    mv = mv[data:] 
    size -= data 

说实话,没有什么是可以保证的。在早期版本的Python中,当你尝试在内存视图上进行'B'类型转换时,可能会出现异常。记住,ctypes.Structure的内存视图是0维的,所以几乎不可能在没有转换的情况下进行切片...

0

首先,你现在是在不断地输出接收到的数据。在每次循环中,你都在重新给变量'datap'赋值。

你可以试试这样做:

datap += sock.recv(sizeof(message) - amount_received)

(当然,这不是最有效率的代码,但你能理解这个思路。)

当你把结构组装好放在'datap'变量里后,可以通过'from_buffer'或'from_buffer_copy'方法把它加载到你的ctype类中。

payload = payload_t.from_buffer_copy(datap)

在你的情况下,后者更好,因为当你重新绑定'datap'变量时,你的缓冲区可能会消失。

撰写回答