实例struct.pack打包文件、解包文件(图像)

这篇博客介绍了如何打包和解包数据集以优化深度学习模型的读取速度。作者提供了Python代码示例,用于打包图片和numpy数组成大文件,然后解包回原始格式。这种方法可以减少读取时间,但需要较大内存。

目录

背景

代码

总结


背景

之前一篇论文代码使用此方式将数据进行了打包,然后再训练,实际上这种方式处理数据集能够提升读取耗费的时间。但是由于电脑性能有限,不得不将打包文件的方式替换掉,故也查了下这种方式打包数据的思路,以后备用。代码未细致整理,但是亲测可用。祝好!

代码

打包

import os
import struct

# 判断文件夹中是否有目标类型图片,没有则返回0
def is_image_file(filename):
    # 如果不都为空、0、false,则any()返回true
    return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)


# 创建图片数据集,存在列表中并返回
def make_dataset(dir):
    images = []
    assert os.path.isdir(dir), '%s is not a valid directory' % dir

    # os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]) 通过在目录树中游走输出在目录中的文件名,top返回三项(root,dirs,files),分别代表:
    # 当前正在遍历的这个文件夹的本身的地址;  list类型,内容是该文件夹中所有的目录的名字(不包括子目录);  list类型,内容是该文件夹中所有的文件(不包括子目录)
    for root, _, fnames in sorted(os.walk(dir)):
        for fname in fnames:
            if is_image_file(fname):
                # print(fname)
                # 拼接出图片的地址,并加入到images列表
                path = os.path.join(root, fname)
                images.append(path)

    return images


def pack(out_dir, indir, target_folders):
    # 遍历存放数据集的文件夹
    for target_folder in target_folders:
        # 拼接生成存放数据集文件夹的路径
        curr_indir = os.path.join(indir, target_folder)
        # 生成的大文件路径(含问文件名)
        curr_out_file = os.path.join(os.path.join(out_dir, '%s.bigfile' % (target_folder)))
        image_lists = make_dataset(curr_indir)
        image_lists.sort()
        with open(curr_out_file, 'wb') as wfid:
            # 写入文件数量
            wfid.write(struct.pack('i', len(image_lists)))
            for i, img_path in enumerate(image_lists):
                # 写入文件名称
                img_name = os.path.basename(img_path)
                img_name_bytes = img_name.encode('utf-8')
                wfid.write(struct.pack('i', len(img_name_bytes)))
                wfid.write(img_name_bytes)

                # 写入图片数据
                with open(img_path, 'rb') as img_fid:
                    img_bytes = img_fid.read()
                wfid.write(struct.pack('i', len(img_bytes)))
                wfid.write(img_bytes)

                if i % 1 == 0:
                    print('write %d files done' % i)


if __name__ == '__main__':
    IMG_EXTENSIONS = [
        '.jpg', '.JPG', '.jpeg', '.JPEG',
        '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.npy'
    ]
    #打包结果存储位置
    out_dir = 'C:/Users/Administrator/Desktop/test/bigdata'
    #待打包文件的文件夹路径
    indir = 'C:/Users/Administrator/Desktop/test'
    #存储待打包文件的文件夹名字
    target_folders = ['image', 'npy']

    pack(out_dir, indir, target_folders)

解包

# -*- coding:utf-8 -*-
import io
import struct
import os
from PIL import Image
import numpy as np

def unpack(file_path,save_path,flag=1):
    print('start load bigfile (%0.02f GB) into memory' % (os.path.getsize(file_path) / 1024 / 1024 / 1024))
    with open(file_path, 'rb') as fid:
        img_num = struct.unpack('i', fid.read(4))[0]
        img_names = []
        img_bytes = []
        print('find total %d images' % img_num)
        for i in range(img_num):
            img_name_len = struct.unpack('i', fid.read(4))[0]
            img_name = fid.read(img_name_len).decode('utf-8')
            img_names.append(img_name)
            img_bytes_len = struct.unpack('i', fid.read(4))[0]
            img_bytes.append(fid.read(img_bytes_len))
            if i % 5000 == 0:
                print('load %d images done' % i)
        print('load all %d images done' % img_num)

    # 返回图片名字和图片
    for index in range(0, len(img_names)):
        try:
            if flag == 1: #解包图片类型
                img = Image.open(io.BytesIO(img_bytes[index])).convert('RGB')
                path_img = os.path.join(save_path,img_names[index])
                img.save(path_img)
            elif flag == 0: #解包npy类型
                npy= np.load(io.BytesIO(img_bytes[index]))
                path_npy = os.path.join(save_path,img_names[index])
                np.save(path_npy,npy)
        except Exception:
            print('file read error for index %d: %s' % (index, img_names[index]))


if __name__ == '__main__':
    #打包图片类型后的文件路径
    filepath1 = 'C:/Users/Administrator/Desktop/test/bigdata/image.bigfile'
    #解包后图片存储地址
    save_path1 = 'C:/Users/Administrator/Desktop/test/out1'

    #打包npy类型后的文件路径
    filepath2 = 'C:/Users/Administrator/Desktop/test/bigdata/npy.bigfile'
    #解包后npy存储地址
    save_path2 = 'C:/Users/Administrator/Desktop/test/out2'

    ##解包图片文件
    #unpack(filepath1,save_path1,flag=1)

    #解包npy矩阵文件
    unpack(filepath2,save_path2,flag=0)

总结

这样就能够得到一个非常大的文件,包含你所有的数据。

在深度学习模型中,使用此方式打包解包来读取文件能够大大减少时间,但是缺陷就是电脑需要较大的内存。

[ERROR] [1753351884.844169]: bad callback: <function point_cloud_callback at 0x7fa17b4b7f70> Traceback (most recent call last): File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 120, in serialize buff.write(_get_struct_3B().pack(*_x)) struct.error: pack expected 3 items for packing (got 16) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 882, in publish self.impl.publish(data) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 1066, in publish serialize_message(b, self.seq, message) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/msg.py", line 152, in serialize_message msg.serialize(b) File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 128, in serialize except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self))))) File "/opt/ros/noetic/lib/python3/dist-packages/genpy/message.py", line 394, in _check_types raise SerializationError(str(exc)) genpy.message.SerializationError: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 748, in _invoke_callback cb(msg, cb_args) File "/home/lixing/catkin_ws/src/pointcloud2_to_custommsg/scripts/pointcloud2_to_custommsg.py", line 49, in point_cloud_callback publisher.publish(livox_msg) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 886, in publish raise ROSSerializationException(str(e)) rospy.exceptions.ROSSerializationException: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' [ERROR] [1753351884.859088]: bad callback: <function point_cloud_callback at 0x7fa17b4b7f70> Traceback (most recent call last): File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 120, in serialize buff.write(_get_struct_3B().pack(*_x)) struct.error: pack expected 3 items for packing (got 16) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 882, in publish self.impl.publish(data) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 1066, in publish serialize_message(b, self.seq, message) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/msg.py", line 152, in serialize_message msg.serialize(b) File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 128, in serialize except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self))))) File "/opt/ros/noetic/lib/python3/dist-packages/genpy/message.py", line 394, in _check_types raise SerializationError(str(exc)) genpy.message.SerializationError: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 748, in _invoke_callback cb(msg, cb_args) File "/home/lixing/catkin_ws/src/pointcloud2_to_custommsg/scripts/pointcloud2_to_custommsg.py", line 49, in point_cloud_callback publisher.publish(livox_msg) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 886, in publish raise ROSSerializationException(str(e)) rospy.exceptions.ROSSerializationException: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' [ERROR] [1753351884.872006]: bad callback: <function point_cloud_callback at 0x7fa17b4b7f70> Traceback (most recent call last): File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 120, in serialize buff.write(_get_struct_3B().pack(*_x)) struct.error: pack expected 3 items for packing (got 16) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 882, in publish self.impl.publish(data) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 1066, in publish serialize_message(b, self.seq, message) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/msg.py", line 152, in serialize_message msg.serialize(b) File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 128, in serialize except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self))))) File "/opt/ros/noetic/lib/python3/dist-packages/genpy/message.py", line 394, in _check_types raise SerializationError(str(exc)) genpy.message.SerializationError: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 748, in _invoke_callback cb(msg, cb_args) File "/home/lixing/catkin_ws/src/pointcloud2_to_custommsg/scripts/pointcloud2_to_custommsg.py", line 49, in point_cloud_callback publisher.publish(livox_msg) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 886, in publish raise ROSSerializationException(str(e)) rospy.exceptions.ROSSerializationException: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' ^C[ERROR] [1753351884.891722]: bad callback: <function point_cloud_callback at 0x7fa17b4b7f70> Traceback (most recent call last): File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 120, in serialize buff.write(_get_struct_3B().pack(*_x)) struct.error: pack expected 3 items for packing (got 16) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 882, in publish self.impl.publish(data) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 1066, in publish serialize_message(b, self.seq, message) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/msg.py", line 152, in serialize_message msg.serialize(b) File "/home/lixing/catkin_ws/devel/lib/python3/dist-packages/livox_ros_driver/msg/_CustomMsg.py", line 128, in serialize except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self))))) File "/opt/ros/noetic/lib/python3/dist-packages/genpy/message.py", line 394, in _check_types raise SerializationError(str(exc)) genpy.message.SerializationError: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 748, in _invoke_callback cb(msg, cb_args) File "/home/lixing/catkin_ws/src/pointcloud2_to_custommsg/scripts/pointcloud2_to_custommsg.py", line 49, in point_cloud_callback publisher.publish(livox_msg) File "/opt/ros/noetic/lib/python3/dist-packages/rospy/topics.py", line 886, in publish raise ROSSerializationException(str(e)) rospy.exceptions.ROSSerializationException: <class 'struct.error'>: 'pack expected 3 items for packing (got 16)' when writing '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]' lixing@lixing:~/catkin_ws/src$
07-25
void CPackDlg::OnBtnPack() { // TODO: Add your control notification handler code here CFileDialog savedlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,"(类型:*.free)|*.free||"); if (IDOK != savedlg.DoModal()) return; int nTimeBegin = time(NULL); int nMiliTimeBegin = timeGetTime(); CString strPathName = savedlg.GetPathName(); CString strExt = savedlg.GetFileExt(); if (strExt!="free") strPathName+=".free"; int nFileNum = m_listFile.GetCount(); m_progress.SetRange(0,nFileNum); FILE* pFilePack = fopen(strPathName.GetBuffer(0),"wb"); if(!pFilePack) return; int nNumWrite = fwrite(&nFileNum;,sizeof(int),1,pFilePack); for (int i=0;i<nFileNum;i++) { CString str; m_listFile.GetText(i,str); int nFileNameLen = str.GetLength(); fwrite(&nFileNameLen;,sizeof(int),1,pFilePack); fwrite(str.GetBuffer(0),1,nFileNameLen,pFilePack); FILE* pFileOri = fopen(str.GetBuffer(0),"rb"); if (!pFileOri) { char buf[256]; sprintf(buf,"文件:\"%s\"不存在,打包失败!",str.GetBuffer(0)); fclose(pFilePack); MessageBox(buf); return; } fseek(pFileOri,0,SEEK_END); int nFileSize = ftell(pFileOri); fwrite(&nFileSize;,1,sizeof(int),pFilePack); fseek(pFileOri,0,SEEK_SET); // //方法一:这种方法效率比较低 // while(nFileSize-->0) // { // char c; // fread(&c,1,1,pFileOri); // fwrite(&c,1,1,pFilePack); // } //方法二:这种方法效率比较高 char *pBuf = new char[nFileSize]; fread(pBuf,1,nFileSize,pFileOri); fwrite(pBuf,1,nFileSize,pFilePack); delete []pBuf; m_progress.SetPos(i+1); fclose(pFileOri); } fclose(pFilePack); int nTimeEnd = time(NULL); int nMiliTimeEnd = timeGetTime(); char bufTime[128] = ""; char bufMiliTime[128] = ""; sprintf(bufTime,"打包用时:%d秒",nTimeEnd-nTimeBegin); sprintf(bufMiliTime,"打包用时:%d毫秒",nMiliTimeEnd-nMiliTimeBegin); if (nTimeEnd-nTimeBegin==0) MessageBox(bufMiliTime); else MessageBox(bufTime); } void CPackDlg::OnBtnUnpack() { // TODO: Add your control notification handler code here CStrin
我感觉是因为在解析Query的时候没有循环读取PTR请求,而是只读取了一次,导致查询中有raop,却不回复该服务 import socket import struct import argparse import random import string import time import threading from collections import defaultdict MDNS_ADDR = “224.0.0.251” MDNS_PORT = 5353 class DNSError(Exception): “”“DNS错误基类”“” pass def generate_random_service_id(length=8): “”“生成8位随机服务ID(大写字母+数字)”“” return ‘’.join(random.choices(string.ascii_uppercase + string.digits, k=length)) def generate_random_hostname_suffix(length=8): “”“生成随机主机名后缀(数字)”“” return ‘’.join(random.choices(string.digits, k=length)) def parse_dns_name(data: bytes, offset: int) -> tuple: “”" 解析DNS名称(支持压缩指针) 返回: (域名, 新偏移量) “”" labels = [] traversed = set() while offset < len(data): length = data[offset] offset += 1 if length == 0: # 结束标记 break # 处理压缩指针 if length & 0xC0 == 0xC0: if offset >= len(data): raise DNSError("压缩指针不完整") ptr_offset = ((length & 0x3F) << 8) | data[offset] offset += 1 if ptr_offset in traversed: break traversed.add(ptr_offset) if ptr_offset >= len(data): break name_part, _ = parse_dns_name(data, ptr_offset) labels.append(name_part) break # 处理普通标签 end = offset + length if end > len(data): break labels.append(data[offset:end].decode('utf-8', 'ignore')) offset = end return '.'.join(labels), offset def build_dns_name(name: str, compress_dict: dict, base_offset: int) -> bytes: “”" 构建DNS名称(支持压缩) compress_dict: {域名: 偏移量} base_offset: 当前数据起始偏移 “”" encoded = b’’ parts = name.split(.) for i in range(len(parts)): partial = '.'.join(parts[i:]) # 应用压缩 if len(partial) > 3 and partial in compress_dict: ptr = compress_dict[partial] if ptr < 0x4000: encoded += struct.pack('!H', 0xC000 | ptr) return encoded # 添加标签 label = parts[i] if not 1 <= len(label) <= 63: raise DNSError(f"无效标签长度: {len(label)}") encoded += bytes([len(label)]) + label.encode('utf-8') # 注册压缩点 current = '.'.join(parts[i:]) pos = base_offset + len(encoded) - len(label) - 1 if len(current) > 3 and current not in compress_dict: compress_dict[current] = pos encoded += b'\x00' return encoded def create_service_response(transaction_id: int, service_type: str, instance_name: str, host_name: str, ip: str, port: int) -> bytes: “”" 创建单服务响应包 “”" compress_dict = {} parts = [] current_offset = 12 # DNS头部长12字节 # === DNS头部 === flags = 0x8400 # QR=1, AA=1 qdcount = 0 # 无查询部分 ancount = 3 # PTR + SRV + A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === PTR记录 === ptr_name = build_dns_name(service_type, compress_dict, current_offset) ptr_data = build_dns_name(instance_name, compress_dict, current_offset + len(ptr_name) + 10) ptr_record = ( ptr_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_data)) + # TYPE=PTR, CLASS=IN, TTL=120 ptr_data ) parts.append(ptr_record) current_offset += len(ptr_record) # === SRV记录 === srv_name = build_dns_name(instance_name, compress_dict, current_offset) srv_data = struct.pack("!HHH", 0, 0, port) # 优先级, 权重, 端口 srv_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_name) + 10 + len(srv_data)) srv_record = ( srv_name + struct.pack("!HHIH", 33, 1, 120, len(srv_data)) + # TYPE=SRV srv_data ) parts.append(srv_record) current_offset += len(srv_record) # === A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = ( a_name + struct.pack("!HHIH", 1, 1, 120, 4) + # TYPE=A a_data ) parts.append(a_record) return b''.join(parts) def create_pair_response(transaction_id: int, airplay_instance: str, raop_instance: str, host_name: str, ip: str) -> bytes: “”" 创建服务对响应包(AirPlay + RAOP) “”" compress_dict = {} parts = [] current_offset = 12 # === DNS头部 === flags = 0x8400 qdcount = 0 ancount = 5 # 2 PTR + 2 SRV + 1 A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === AirPlay PTR === ptr_ap_name = build_dns_name("_airplay._tcp.local", compress_dict, current_offset) ptr_ap_data = build_dns_name(airplay_instance, compress_dict, current_offset + len(ptr_ap_name) + 10) ptr_ap_record = ptr_ap_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_ap_data)) + ptr_ap_data parts.append(ptr_ap_record) current_offset += len(ptr_ap_record) # === AirPlay SRV === srv_ap_name = build_dns_name(airplay_instance, compress_dict, current_offset) srv_ap_data = struct.pack("!HHH", 0, 0, 5000) srv_ap_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_ap_name) + 10 + len(srv_ap_data)) srv_ap_record = srv_ap_name + struct.pack("!HHIH", 33, 1, 120, len(srv_ap_data)) + srv_ap_data parts.append(srv_ap_record) current_offset += len(srv_ap_record) # === RAOP PTR === ptr_raop_name = build_dns_name("_raop._tcp.local", compress_dict, current_offset) ptr_raop_data = build_dns_name(raop_instance, compress_dict, current_offset + len(ptr_raop_name) + 10) ptr_raop_record = ptr_raop_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_raop_data)) + ptr_raop_data parts.append(ptr_raop_record) current_offset += len(ptr_raop_record) # === RAOP SRV === srv_raop_name = build_dns_name(raop_instance, compress_dict, current_offset) srv_raop_data = struct.pack("!HHH", 0, 0, 7000) srv_raop_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_raop_name) + 10 + len(srv_raop_data)) srv_raop_record = srv_raop_name + struct.pack("!HHIH", 33, 1, 120, len(srv_raop_data)) + srv_raop_data parts.append(srv_raop_record) current_offset += len(srv_raop_record) # === 共享A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = a_name + struct.pack("!HHIH", 1, 1, 120, 4) + a_data parts.append(a_record) return b''.join(parts) class MDNSResponder: def init(self, iface_ip: str): “”“mDNS响应器初始化”“” self.iface_ip = iface_ip self.services = [] # 所有服务对 self.service_map = defaultdict(list) # 服务类型到实例的映射 self.running = False # 创建socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 多播设置 self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) try: self.sock.bind(("", MDNS_PORT)) mreq = struct.pack("!4s4s", socket.inet_aton(MDNS_ADDR), socket.inet_aton(iface_ip)) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) print(f"[mDNS] Socket bound to {MDNS_ADDR}:{MDNS_PORT}") except Exception as e: raise RuntimeError(f"Socket初始化失败: {e}") def generate_services(self, start_ip: str, count: int): """生成服务对(每对包含AirPlay和RAOP服务)""" base_ip, start_num = start_ip.rsplit('.', 1) start_num = int(start_num) for i in range(count): # 生成递增IP地址 ip = f"{base_ip}.{start_num + i}" # 生成唯一服务ID(8位字符) service_id = generate_random_service_id() # 生成主机名(主机名后缀为8位数字) host_suffix = generate_random_hostname_suffix() host_name = f"host-{host_suffix}.local" # AirPlay实例名 airplay_instance = f"{service_id}._airplay._tcp.local" # RAOP实例名(遵循Apple规范) raop_instance = f"{service_id}@AirPlay._raop._tcp.local" # 添加到服务列表 self.services.append({ "ip": ip, "host_name": host_name, "airplay_instance": airplay_instance, "raop_instance": raop_instance }) # 添加到服务映射 self.service_map["_airplay._tcp.local"].append( (airplay_instance, host_name, ip, 5000) ) self.service_map["_raop._tcp.local"].append( (raop_instance, host_name, ip, 7000) ) print(f"[生成服务] 已创建 {count} 对服务") def start(self): """启动mDNS响应器""" self.running = True print(f"[mDNS响应器] 在 {self.iface_ip} 上启动") while self.running: try: data, addr = self.sock.recvfrom(2048) if len(data) < 12: continue # 处理数据包 self.handle_packet(data, addr) except Exception as e: if self.running: # 防止关闭时的报错 print(f"处理报文错误: {e}") def handle_packet(self, data: bytes, addr: tuple): """处理接收到的mDNS查询""" # 解析头部 transaction_id = struct.unpack("!H", data[0:2])[0] flags = struct.unpack("!H", data[2:4])[0] # 只处理查询请求 if flags & 0x8000: return # 解析查询的服务类型 try: offset = 12 qname, _ = parse_dns_name(data, offset) except DNSError as e: return # 检查是否查询支持的服务 service_type = qname.lower() if service_type not in self.service_map: return print(f"[查询] 来自 {addr[0]} 查询 {service_type}") # 为该服务类型的所有实例发送响应 for instance_info in self.service_map[service_type]: instance_name, host_name, ip, port = instance_info try: response = create_service_response( transaction_id, service_type, instance_name, host_name, ip, port ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) except Exception as e: print(f"构建响应错误: {e}") def stop(self): """停止响应器""" self.running = False self.sock.close() print("[mDNS响应器] 已停止") class ServiceAnnouncer: def init(self, iface_ip: str, services: list): “”“服务广播器初始化”“” self.iface_ip = iface_ip self.services = services self.running = False # 创建广播socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) def start(self): """启动服务广播""" self.running = True print("[广播器] 开始周期性广播服务") broadcast_count = 0 while self.running: try: broadcast_count += 1 broadcast_time = time.strftime("%Y-%m-%d %H:%M:%S") print(f"\n[广播 #{broadcast_count}] 开始于 {broadcast_time}") # 广播每个服务对 for idx, service in enumerate(self.services): transaction_id = random.randint(0, 0xFFFF) try: response = create_pair_response( transaction_id, service["airplay_instance"], service["raop_instance"], service["host_name"], service["ip"] ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) print(f" 已广播服务对 #{idx+1}/{len(self.services)}: {service['ip']}") except Exception as e: print(f" 广播错误: {e}") # 等待下次广播 sleep_interval = 120 # 2分钟 for _ in range(sleep_interval): if not self.running: break time.sleep(1) except Exception as e: print(f"广播错误: {e}") def stop(self): """停止广播""" self.running = False self.sock.close() print("[广播器] 已停止") def main(): parser = argparse.ArgumentParser(description=“AirPlay & RAOP mDNS 批量响应器”) parser.add_argument(“–iface_ip”, required=True, help=“网络接口IP”) parser.add_argument(“–start_ip”, default=“192.168.0.2”, help=“起始IP地址 (默认: 192.168.0.2)) parser.add_argument(“–count”, type=int, default=100, help=“服务对数量 (默认: 100)) args = parser.parse_args() # 创建响应器 responder = MDNSResponder(args.iface_ip) # 生成服务 responder.generate_services(args.start_ip, args.count) # 启动响应线程 responder_thread = threading.Thread(target=responder.start, daemon=True) responder_thread.start() print("服务已启动,按Ctrl+C退出...") try: # 主线程等待中断信号 while True: time.sleep(1) except KeyboardInterrupt: print("正在停止...") # 设置停止标志(如果类中有) responder.running = False # 也可以调用stop方法,但注意线程可能阻塞在recvfrom或sleep中 # 所以设置标志后,还需要中断这些阻塞(例如关闭socket) responder.stop() # 等待线程结束(如果有必要) responder_thread.join(timeout=1.0) print("已退出") if name == “main”: main()
最新发布
10-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alocus_

如果我的内容帮助到你,打赏我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值