经常我们要发送的信息是结构化的数据,此时发送和接收数据结构就是一个很基本的工作,怎样来实现呢?
发送和接收数据结构我们要用到 python 的 struct 模块,该模块可以用 struct来处理c语言中的结构体。
首先来看下struct中支持的数据类型
Format | C Type | Python | 字节数 |
---|---|---|---|
x | pad byte | no value | 1 |
c | char | string of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I | unsinged int | integer or long | 4 |
l | long | integer | 4 |
L | unsigned long | long | 4 |
q | long long | long | 8 |
Q | unsigned long long | long | 8 |
f | float | float | 4 |
d | double | float | 8 |
s | char[] | string | 1 |
p | char[] | string | 1 |
P | void * | long |
struct模块中最重要的三个函数是pack(), unpack(), calcsize()
pack(fmt, v1, v2, …) 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)
unpack(fmt, string) 按照给定的格式(fmt)解析字节流string,返回解析出来的tuple(元组)
calcsize(fmt) 计算给定的格式(fmt)占用多少字节的内存
这里需要注意的是字节对齐的情况,字节对齐,简单来讲就是说比如一个 char 类型字符本来占1个字节,但是在结构体中,根据前后数据类型的不同,为了计算机读取内存的方便,会按照一定的规则给该 char 类型数据分配不只1个字节,可能是2个,那多出来的字节会导致我们按照各个数据类型长度算出来的与实际的不一样,我们可以写一段验证代码(下面是一段C++代码,因为用python来写的话还要用到一个库,下面会说到):
#include
struct A
{
double num1;
double num2;
bool num3;
};
#pragma pack(push,1)
struct B
{
double num1;
double num2;
bool num3;
};
#pragma pack(pop)
int main()
{
printf("length of A: %d\nlength of B: %d\n", sizeof(A), sizeof(B));
return 0;
}
结果是:
length of A: 24
length of B: 17
1个double类型数据8个字节,1个bool类型数据1个字节,应该是17个字节,但是A的长度确实24个字节,相当于是3个double的长度,这就是字节对齐。而B的定义前后都加了一句话,
#pragma pack(push,1) 作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐
#pragma pack(pop) 作用:恢复对齐状态
加上这两句话,就使得结构体的内存是连续的。
下面就可以用unpack()来进行数据解析了,要注意的是:字节对齐也有不同的规则,比如大端对齐、小端对齐等,所以需要struct用格式中的第一个字符来改变对齐方式。如下:
character | byte order | size | aligenment |
---|---|---|---|
@ | native | native | native |
= | native | standard | none |
< | little-endian | standard | none |
> | big-endian | standard | none |
! | network(= big-endian) | standard | none |
用法就是在unpack(fmt, string)中的 fmt 加上符号,比如 ’<2i3d’表示将数据解析为小端对齐方式的2个int类型和3个double类型。
发送端是c++代码,接收端是python。
发送端
#include
#include
#include
#include
#include
#define PORT 8000
#pragma pack(push,1)
struct B //要发送的数据结构
{
double num1;
double num2;
bool num3;
};
#pragma pack(pop)
int main(void)
{
//实例化结构体,并赋值
B message;
message.num1=1200;
message.num2=3000;
message.num3=1;
//定义socket套字
int sock;
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr)); //把servaddr内存清零
sock = socket(AF_INET, SOCK_DGRAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr("192.168.43.131");
while (1)
{
printf("向服务器发送:%.f, %.f, %d\n",message.num1,message.num2,message.num3);
sendto(sock, &message, sizeof(B), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
sleep(1);
}
close(sock);
return 0;
}
接收端
# -*- coding: utf-8 -*-
import socket
import time
import struct
PORT = 8000
receiver_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ("192.168.43.131", PORT)
receiver_socket.bind(address)
while True:
now = time.time()
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(now)))
receive_message, client = receiver_socket.recvfrom(1024)
data = struct.unpack('<2d1b',receive_message)
print 'num1:' ,data[0], ' num2:',data[1], ' num3:',data[2]
思路就是接收端定义一个与发送端相同的结构体,将socket接收的数据拷贝到结构体中。
需要用到 ctype 模块,ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C 动态链接库中的函数。
ctypes的类型对应如下:
ctypes type | C type | Python Type |
---|---|---|
c_char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_bool | bool | bool |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned int | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or longlong | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_longdouble | long double float | float |
c_char_p | char * | string or None |
c_wchar_p | wchar_t * | unicode or None |
c_void_p | void * | int/long or None |
对应的指针类型是在后面加上"_p",如int*是c_int_p等等。在python中要实现c语言中的结构,需要用到类。
下面是发送和接收的代码:
发送端 struct_sender.py
# -*- coding: utf-8 -*-
import socket
import time
import struct
from ctypes import *
class Data(Structure):
_pack_ = 1 #让结构体内存连续
_fields_ = [("member_1", c_ubyte),
("member_2", c_ubyte),
("member_3", c_float),
("member_4", c_float),
("member_5", c_double)]
data = Data()
data.member_1= 0xF2
data.member_2= 0x01
data.member_3= 125
data.member_4= 150
data.member_5= 1000
PORT = 8000
sender_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
receiver_address = ("192.168.43.131", PORT)
while True:
start = time.time()
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start)))
sender_socket.sendto(data, receiver_address)
print 'member_1: %d' % data.member_1, 'member_2: %d' % data.member_2, 'member_3: %.2f' % data.member_3, \
'member_4: %.2f' % data.member_4, 'member_5: %d' % data.member_5
time.sleep(1)
接收端 struct_receiver.py
# -*- coding: utf-8 -*-
import socket
import time
import struct
from ctypes import *
class Data(Structure):
_pack_ = 1
_fields_ = [("member_1", c_ubyte),
("member_2", c_ubyte),
("member_3", c_float),
("member_4", c_float),
("member_5", c_double)]
data = Data()
PORT = 8000
receiver_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ("192.168.43.131", PORT)
receiver_socket.bind(address)
while True:
now = time.time()
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(now)))
message, client = receiver_socket.recvfrom(1024)
memmove(addressof(data), (message), sizeof(Data))
print 'member_1: %d' % data.member_1, 'member_2: %d' % data.member_2, 'member_3: %.2f' % data.member_3, \
'member_4: %.2f' % data.member_4, 'member_5: %d' % data.member_5