Python struct模块与简单使用

简介

struct模块常常用在网络编程中,

  • 将要发送的数据转换成字节流形式使用函数struct.pack()
  • 将收到的字节流解析成具体数据使用函数struct.unpack()
  • 计算格式字符串的长度使用函数struct.calcsize()

它们是struct模块中最常使用的函数,其函数声明为:

  • pack(fmt, v1, v2, ...) ------ 根据所给的fmt描述的格式将值v1,v2,…转换为一个字符串。
  • unpack(fmt, bytes) ------ 根据所给的fmt描述的格式将bytes反向解析出来,返回一个元组。
  • calcsize(fmt) ------ 根据所给的fmt描述的格式返回该结构的大小。

重点

可以发现,这三个函数中都有一个fmt的格式字符串,下面给出的是格式字符串中字符的实际含义:

在这里插入图片描述
网络发送数据时,有字节序的问题,它的字符含义如下:

在这里插入图片描述

以一个简单的网络CS程序为例,服务器程序等待客户端的连接,连接成功后,接收客户端发送的信息并输出;客户端程序连接服务器程序,然后等待输入并发送给服务器程序。

网络编程中,发送端会先发送一个包头,再发送具体的包体(实际发送数据)。包头的意义在于标识出包体数据的长度,包头的长度是固定的,事先预定好的。接收端会先接收的固定长度的包头,再根据包头中标识包体长度的字段来决定接收多少包体数据。例如,包头的结构如下:

struct msg_header
{
	unsigned short unused1; // 暂未使用
	unsigned int msgBodyLen; // 包体长度
	unsigned long long unuse2; // 暂未使用
}

msg_header对应的fmt'HIL',根据上面表中信息不难看懂其中的对应关系。
这里没有使用字节序对应的字符。只要客户端与服务端对应即可,如果客户端使用小端字节序,服务端也需要使用小端字节序;如果客户端使用大端字节序,服务端也必须使用大端字节序

客户端发送数据的代码片段如下:

client_sock = socket.socket()
...
send_msg = input()
msg_header_fmt = 'HIL' # 包头的格式,与msg_header中字段一一对应
msg_header_data = struct.pack(msg_header_fmt, 0, len(send_msg), 0) # 包头中只有到msgBody字段
client_sock.sendall(msg_header_data)
msg_body_data = send_msg.encode() # send_msg类型为str,发送时需要使用encode()转换成二进制数据
client_sock.sendall(msg_body_data)

客户端发送数据时先发送包头再发送包体,当然也可以包头包体一起发送:

send_msg = input('input:')
msg_fmt = 'HIL{}s'.format(len(send_msg))  # 包头包体的格式
msg_data = struct.pack(msg_fmt, 0, len(send_msg), 0, send_msg.encode()) # 包头中只用到msgBody字段
self.sock .sendall(msg_data)

msg_fmt = 'HIL{}s'.format(len(send_msg))会根据发送数据的长度生成对应的fmt,例如发送的数据为'hello world'msg_fmt 等于就'HIL11s',也就表示包头后面跟着长度为11个字节的包体。

服务端接收数据的代码片段如下:

def recv_some(client_sock, data_len)):
	...

msg_header_format = 'HIL'
msg_header_len = struct.calcsize(msg_header_format) # 包头长度

# 接收包头收据
recv_suc, msg_header_data = recv_some(client_sock, msg_header_len)
if not recv_suc:
    break

# 获取包体长度(只取包体长度字段) 注意strcut.unpack()返回的是一个tuple
_, msg_body_len, _  = struct.unpack(msg_header_format, msg_header_data)

# 接收包体数据
recv_suc, msg_body_data = recv_some(client_sock, msg_body_len)
if not recv_suc:
    break

# msg_body_data为二进制数据,需要使用decode()进行装换
print('recv msg: {}'.format(msg_body_data.decode()))

示例

客户端代码为:

import socket
import struct

class TestClient:
    def __init__(self, ip_addr, port):
        self.ip_addr = ip_addr
        self.port = port
        self.sock = socket.socket()

    def start(self):
        print('connect to {}:{}'.format(self.ip_addr, self.port))
        self.sock.connect((self.ip_addr, self.port))
        print('connect OK')

        while True:
            send_msg = input('input:')
            msg_header_fmt = 'HIL'  # 包头的格式,与msg_header中字段一一对应
            msg_header_data = struct.pack(msg_header_fmt, 0, len(send_msg), 0) # 包头中只有到msgBody字段
            msg_body_data = send_msg.encode()
            self.sock .sendall(msg_header_data + msg_body_data)


if __name__ == '__main__':
    one_client = TestClient('127.0.0.1', 10088)
    one_client.start()

服务端代码为:

import socket
import struct

def recv_some(client_sock, data_len):
    recv_data = b''
    remain_len = data_len

    while True:
        try:
            cur_data = client_sock.recv(remain_len)
        except ConnectionResetError as error:
            return False, recv_data

        recv_data += cur_data
        remain_len -= len(cur_data)
        if remain_len == 0:
            break

    return True, recv_data


class TestServer:
    def __init__(self, ip_addr, port):
        self.ip_addr = ip_addr
        self.port = port
        self.sock = socket.socket()

    def start(self):
        self.sock.bind((self.ip_addr, self.port))
        self.sock.listen(5)

        print('server listen on {}:{}'.format(self.ip_addr, self.port))
        client_sock, client_info = self.sock.accept()
        print('recv connection from {}'.format(client_info))

        msg_header_format = 'HIL'
        msg_header_len = struct.calcsize(msg_header_format)

        while True:
            # 接收包头
            recv_suc, msg_header_data = recv_some(client_sock, msg_header_len)
            if not recv_suc:
                break

            # 获取包体长度
            _, msg_body_len, _  = struct.unpack(msg_header_format, msg_header_data)

            # 接收包体数据
            recv_suc, msg_body_data = recv_some(client_sock, msg_body_len)
            if not recv_suc:
                break

            print('recv msg: {}'.format(msg_body_data.decode()))

if __name__ == '__main__':
    one_server = TestServer('127.0.0.1', 10088)
    one_server.start()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
struct模块Python中用于处理二进制数据的模块。它提供了一种通过定义结构体来解析和打包数据的方法,用于处理不同字节长度和字节顺序的数据。 使用struct模块,你可以将数据打包成二进制字符串,或者从二进制字符串中解析出特定类型的数据。这在处理网络通信、文件读写、操作硬件设备等场景下非常有用。 要使用struct模块,你首先需要导入它,然后可以使用其中的函数来执行各种操作。一些常用的函数包括: - struct.pack(format, v1, v2, ...):将指定的值按照给定的格式(format)打包成二进制字符串。 - struct.unpack(format, string):按照给定的格式(format)从二进制字符串中解析出对应的值。 - struct.calcsize(format):返回给定格式(format)的结构体所占用的字节数。 在format参数中,你可以使用各种格式化字符来表示不同类型的数据,如整数、浮点数、字符串等,并可以指定字节顺序和字节对齐方式。 例如,以下是一个使用struct模块打包和解析数据的示例: ```python import struct # 打包数据 packed_data = struct.pack('iif', 1, 2, 3.14) print(packed_data) # 输出:b'\x01\x00\x00\x00\x02\x00\x00\x00\xd0\x0f\x49\x40' # 解析数据 unpacked_data = struct.unpack('iif', packed_data) print(unpacked_data) # 输出:(1, 2, 3.140000104904175) ``` 在上面的示例中,我们使用'iif'格式来表示一个整数、一个整数和一个浮点数。通过pack函数打包这些数据后,得到了一个二进制字符串。然后,使用unpack函数从该二进制字符串中解析出相应的值。 除了上述常用的函数和格式化字符外,struct模块还提供了其他一些函数和格式化字符,用于处理更复杂的数据结构。你可以查阅官方文档来了解更多详细信息。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值