【问题标题】:What is the best way to write a struct to file?将结构写入文件的最佳方法是什么?
【发布时间】:2017-06-04 17:15:17
【问题描述】:

我有这两个结构:

struct pcap_hdr_s {
    UInt32 magic_number;
    UInt16 version_major;
    UInt16 version_minor;
    int32_t thiszone;
    UInt32 sigfigs;
    UInt32 snaplen;
    UInt32 network;
};
//packet header
struct pcaprec_hdr_s {
    UInt32 ts_sec;
    UInt32 ts_usec;
    UInt32 incl_len;
    UInt32 orig_len;
};

初始化如下(例如):

    let pcapHeader : pcap_hdr_s = pcap_hdr_s(magic_number: 0xa1b2c3d4,
                                            version_major: 2, 
                                            version_minor: 4, 
                                            thiszone: 0, 
                                            sigfigs: 0,
                                            snaplen: 
                                            pcap_record_size, 
                                            network: LINKTYPE_ETHERNET)

    let pcapRecHeader : pcaprec_hdr_s = pcaprec_hdr_s(ts_sec: UInt32(ts.tv_sec),
                                        ts_usec: UInt32(ts.tv_nsec), 
                                        incl_len: plen, 
                                        orig_len: length) 

我尝试像这样创建结构的 Data/NSData 对象:

            //write pcap header
            let pcapHeaderData : NSData = NSData(bytes: pcapHeader, length: sizeofValue(pcapHeader))
            //write pcaprec header
            let pcapRecHeaderData : NSData = NSData(bytes: pcapRecHeader, length: sizeofValue(pcapRecHeader))

但我总是在每一行得到这个错误:

"Connot convert value if type 'pcap_hdr_s' to expected arguemnt type 'UsafeRawPointer?'"

我查看了 Swift 中 UnsafeRawPointers 的文档,但目前还没有足够的了解,无法从结构中创建 NSData 对象。 我是在正确的道路上还是有更好的方式来实现我的意图?

如果这个数据初始化可行,我的下一步将是

  • 将 pcapRecHeaderData 附加到 pcapHeaderData
  • 使用Data/NSData提供的函数将pcapHeaderData原子写入文件/url

编辑:

//packet ethernet header
struct ethernet_hdr_s {
    let dhost : [UInt8]
    let shost : [UInt8]
    let type : UInt16
};

let src_mac : [UInt8] = [0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB]
let dest_mac : [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]

let ethernetHeader : ethernet_hdr_s = ethernet_hdr_s(dhost: dest_mac, shost: src_mac, type: 0x0800)

编辑 2:

let payloadSize = packet.payload.count
            let plen = (payloadSize < Int(pcap_record_size) ? payloadSize : Int(pcap_record_size));

            bytesWritten = withUnsafePointer(to: &(packet.payload)) {
                $0.withMemoryRebound(to: UInt8.self, capacity: Int(plen)) {
                    ostream.write($0, maxLength: Int(plen))
                }
            }
            if bytesWritten != (Int(plen)) {
                // Could not write all bytes, report error ...
                NSLog("error in Writting packet payload, not all Bytes written: bytesWritten: %d|plen: %d", bytesWritten, Int(plen))
            }

【问题讨论】:

    标签: ios swift struct nsdata


    【解决方案1】:

    您可以将任意数据写入InputStream,而无需创建 (NS)Data 对象优先。 “挑战”是如何将指针转换为 write 方法所期望的指向 UInt8 指针的结构:

    let ostream = OutputStream(url: url, append: false)! // Add error checking here!
    ostream.open()
    
    var pcapHeader = pcap_hdr_s(...)
    let headerSize = MemoryLayout.size(ofValue: pcapHeader)
    
    let bytesWritten = withUnsafePointer(to: &pcapHeader) {
            $0.withMemoryRebound(to: UInt8.self, capacity: headerSize) {
            ostream.write($0, maxLength: headerSize)
        }
    }
    if bytesWritten != headerSize {
        // Could not write all bytes, report error ...
    }
    

    同样你可以从InputStream中读取数据:

    let istream = InputStream(url: url)! // Add error checking here!
    istream.open()
    
    let bytesRead = withUnsafeMutablePointer(to: &pcapHeader) {
        $0.withMemoryRebound(to: UInt8.self, capacity: headerSize) {
            istream.read($0, maxLength: headerSize)
        }
    }
    if bytesRead != headerSize {
        // Could not read all bytes, report error ...
    }
    

    如果文件可能是在不同的平台上创建的 不同的字节顺序然后你可以检查“魔术”和交换字节 如有必要(如https://wiki.wireshark.org/Development/LibpcapFileFormat 所述):

    switch pcapHeader.magic_number {
    case 0xa1b2c3d4:
        break // Already in host byte order
    case 0xd4c3b2a1:
        pcapHeader.version_major = pcapHeader.version_major.byteSwapped
        pcapHeader.version_minor = pcapHeader.version_minor.byteSwapped
        // ...
    default:
        // Unknown magic, report error ...
    }
    

    为了简化读写结构的任务,我们可以定义 自定义扩展方法,例如

    extension OutputStream {
    
        enum ValueWriteError: Error {
            case incompleteWrite
            case unknownError
        }
    
        func write<T>(value: T) throws {
            var value = value
            let size = MemoryLayout.size(ofValue: value)
            let bytesWritten = withUnsafePointer(to: &value) {
                $0.withMemoryRebound(to: UInt8.self, capacity: size) {
                    write($0, maxLength: size)
                }
            }
            if bytesWritten == -1 {
                throw streamError ?? ValueWriteError.unknownError
            } else if bytesWritten != size {
                throw ValueWriteError.incompleteWrite
            }
        }
    } 
    
    extension InputStream {
    
        enum ValueReadError: Error {
            case incompleteRead
            case unknownError
        }
    
        func read<T>(value: inout T) throws {
            let size = MemoryLayout.size(ofValue: value)
            let bytesRead = withUnsafeMutablePointer(to: &value) {
                $0.withMemoryRebound(to: UInt8.self, capacity: size) {
                    read($0, maxLength: size)
                }
            }
            if bytesRead == -1 {
                throw streamError ?? ValueReadError.unknownError
            } else if bytesRead != size {
                throw ValueReadError.incompleteRead
            }
        }
    }
    

    现在你可以简单地写和读

    try ostream.write(value: pcapHeader)
    try istream.read(value: &pcapHeader) 
    

    当然,这只适用于像你这样的“自包含”结构 pcap_hdr_spcaprec_hdr_s

    【讨论】:

    • 感谢您的出色回答,但我被卡在了将文件 url 传递给 OutputStream 的地方。在将 url 传递给 Outputstream 之前是否要创建文件,还是由流本身创建?
    • @student96:我想现在已经澄清了:)
    • 是的,也感谢其他答案。我虽然这不是同一个问题,所以我为此创建了一个新线程;)
    • 您是否知道为什么 ethernet_hdr_s 结构(在我的问题的编辑部分中初始化)的长度为 10 而不是这一行中的 14:let ethernetheaderSize = MemoryLayout.size(ofValue : packet.ethernetHeader)
    • @student96: [UInt8] 是一个 Swift 数组:一个固定大小的结构体,带有指向元素存储的(不透明的)指针。一个 C 数组作为元组导入 Swift:(UInt8, ..., UInt8)。 - 但实际上现在这是一个完全不同的问题:)
    【解决方案2】:

    您可以在 Swift 3 中将 pcap_hdr_s 转换为 Data,反之亦然

    • pcap_hdr_s -> Data

      var pcapHeader : pcap_hdr_s = pcap_hdr_s(magic_number ...
      
      let data = withUnsafePointer(to: &pcapHeader) {
           Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: pcapHeader))
      }
      
    • Data -> pcap_hdr_s

      let header: pcap_hdr_s = data.withUnsafeBytes { $0.pointee }
      

    参考:round trip Swift number types to/from Data

    【讨论】:

    • 如果(好吧,何时)ABI 发生变化,保存这样的数据会中断
    • @Alexander ABI 是什么?
    • ABI 是“应用程序二进制接口”。 – @Alexander:pcap_hdr_s 结构(记录在wiki.wireshark.org/Development/LibpcapFileFormat)具有“神奇”和版本号,足以防止读取不兼容的数据。
    猜你喜欢
    • 2020-09-01
    • 1970-01-01
    • 2011-03-16
    • 2020-08-26
    • 2014-02-13
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多