【问题标题】:Read exactly n bytes from InputStream in Swift 4在 Swift 4 中从 InputStream 中准确读取 n 个字节
【发布时间】:2018-06-28 16:08:41
【问题描述】:

我有一个服务器通过 TCP 向我发送消息,其中前 4 个字节确定消息其余部分的长度。所以我需要

1) 将 4 个字节读入 UInt32(有效)并将其存储到 bytes_expected

2) 将 bytes_expected 个字节读入 message

现在我的代码如下所示:

private let inputStreamAccessQueue  = DispatchQueue(label: "SynchronizedInputStreamAccess")

func inputStreamHandler(_ event: Stream.Event) {
    switch event {
        case Stream.Event.hasBytesAvailable:
            self.handleInput()

        ...
    }
}

func handleInput() {
    // **QUESTION: Do I use this barrier wrong?**
    self.inputStreamAccessQueue.sync(flags: .barrier) {            
        guard let istr = self.inputStream else {
            log.error(self.buildLogMessage("InputStream is nil"))
            return
        }

        guard istr.hasBytesAvailable else {
            log.error(self.buildLogMessage("handleInput() called when inputstream has no bytes available"))
            return
        }

        let lengthbuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 4)
        defer { lengthbuffer.deallocate(capacity: 4) }
        let lenbytes_read = istr.read(lengthbuffer, maxLength: 4)

        guard lenbytes_read == 4 else {
            self.errorHandler(NetworkingError.InputError("Input Stream received \(lenbytes_read) (!=4) bytes"))
            return
        }

        let bytes_expected = Int(UnsafeRawPointer(lengthbuffer).load(as: UInt32.self).bigEndian)
        log.info(self.buildLogMessage("expect \(bytes_expected) bytes"))

        print("::DEBUG", call, "bytes_expected", bytes_expected)

        var message = ""
        var bytes_missing = bytes_expected
        while bytes_missing > 0 {
            //print("::DEBUG", call, "bytes_missing", bytes_missing)
            let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bytes_missing)
            let bytes_read = istr.read(buffer, maxLength: bytes_missing)

            print("::DEBUG", call, "bytes_read", bytes_read)

            guard bytes_read > 0 else {
                print("bytes_read not > 0: \(bytes_read)")
                return
            }

            guard bytes_read <= bytes_missing else {
                print("Read more bytes than expected. missing=\(bytes_missing), read=\(bytes_read)")
                return
            }

            guard let partial_message = String(bytesNoCopy: buffer, length: bytes_read, encoding: .utf8, freeWhenDone: true) else {
                log.error("ERROR WHEN READING")
                return
            }

            message = message + partial_message
            bytes_missing -= bytes_read
        }

        self.handleMessage(message)
    }
}

我的问题是 istr.read(buffer, maxLength: bytes_missing) 有时不会一次读取所有消息,所以我循环直到我读完我想要的所有消息。但是我仍然看到我的应用程序崩溃(很少),因为再次调用了 handleInput() 而对该方法的另一个调用仍在运行。在这种情况下,bytes_expected 包含随机值,应用程序由于非法内存分配而崩溃。

我想我可以通过使用屏障来避免这种情况。但这似乎不起作用……我是不是用错了屏障?

【问题讨论】:

  • 是的,但是 cmets 中的一个人告诉我要提出一个新问题。最后一个问题我不小心使用了一个旧帐户。我删除了旧帐户,将来会使用这个帐户
  • 是的,这里有一个新问题是正确的,我只是想知道不同的用户名。
  • 我没有时间写一个完整的答案,但我的建议是不要对抗网络IO的异步性质,并且不要 使用屏障。 – 在handleInput() 中,只需在缓冲区中收集可用数据。如果缓冲区包含足够的数据(4 个字节用于计数器 + 预期字节数),则对其进行处理并将其从缓冲区中删除。否则什么都不做,等待更多数据。
  • 我没有使用该库的经验,也没有建议,抱歉。

标签: ios swift tcp network-programming inputstream


【解决方案1】:

我的建议是不要与网络 I/O 的异步特性作斗争。 每当Stream.Event.hasBytesAvailable 事件发生时读取并收集缓冲区中的数据 发出信号。如果缓冲区包含足够的数据(4 个长度字节加上 预期的消息长度)然后处理数据并将其删除。否则什么都不做 并等待更多数据。

以下(未经测试的)代码仅用于演示。 它仅显示与此特定问题相关的部分。 为简洁起见,省略了初始化、事件处理程序等。

class MessageReader {

    var buffer = Data(count: 1024) // Must be large enough for largest message + 4
    var bytesRead = 0 // Number of bytes read so far

    // Called from `handleInput` with a complete message.
    func processMessage(message: Data) {
        // ...
    }

    // Called from event handler if `Stream.Event.hasBytesAvailable` is signalled.
    func handleInput(istr: InputStream) {
        assert(bytesRead < buffer.count)

        // Read from input stream, appending to previously read data:
        let maxRead = buffer.count - bytesRead
        let amount = buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
            istr.read(p + bytesRead, maxLength: maxRead)
        }
        guard amount > 0 else {
            // handle EOF or read error ...
            fatalError()
        }
        bytesRead += amount

        while bytesRead >= 4 {
            // Read message size:
            let messageSize = buffer.withUnsafeBytes { (p: UnsafePointer<UInt32>) in
                Int(UInt32(bigEndian: p.pointee))
            }
            let totalSize = 4 + messageSize
            guard totalSize <= buffer.count else {
                // Handle buffer too small for message situation ...
                fatalError()
            }

            if bytesRead < totalSize {
                break // Not enough data to read message.
            }

            // Buffer contains complete message now. Process it ...
            processMessage(message: buffer[4 ..< totalSize])

            // ... and remove it from the buffer:
            if totalSize < bytesRead {
                // Move remaining data to the front:
                buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
                    _ = memmove(p, p + totalSize, bytesRead - totalSize)
                }
            }
            bytesRead -= totalSize
        }
    }
}

【讨论】:

  • 非常感谢!如果我找不到工作库,我会试试这个
  • 我收到错误“同时访问 0x16584fe0,但修改需要独占访问。”在我读入缓冲区的行中:istr.read(p + bytesRead, maxLength: buffer.count - bytesRead) 这似乎是 Swift 4 中的一个新功能......我该如何避免这种情况?
  • @ITStudent:Swift 运行时抱怨是因为buffer.count 在突变buffer 时被读取。可以通过预先计算 maxLength 来避免。
  • 似乎有效(我修改了你的想法,但它仍然有很大帮助!)stackoverflow.com/a/48360431/3827381
  • “我的建议是不要与网络 I/O 的异步特性作斗争。”这正是我需要的智慧。谢谢你。
【解决方案2】:

受 Martin R 的启发(https://stackoverflow.com/a/48344040/3827381 - 非常感谢!)我想出了这个解决方案:

var buffer = Data(count: 4096)
var offset = 0 // the index of the first byte that can be overridden
var readState = 0
var missingMsgBytes = 0
var msg = ""

func handleInput(_ istr: InputStream) {
    assert(buffer.count >= 5, "buffer must be large enough to contain length info (4 bytes) and at least one payload byte => min 5 bytes buffer required")
    assert(offset < buffer.count, "offset \(offset) is not smaller than \(buffer.count)")

    let toRead = buffer.count - offset
    let read = buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in istr.read(p + offset, maxLength: toRead) }
    guard read > 0 else {
        self.errorHandler(NetworkingError.InputError("Input Stream received \(read) bytes which is smaller than 0 => Network error"))
        return
    }
    offset += read
    var msgStart = 0
    var msgEnd = 0

    if readState == 0 {
        if offset < 4 {
            return
        }
        missingMsgBytes = buffer[0...3].withUnsafeBytes { (p: UnsafePointer<UInt32>) in Int(UInt32(bigEndian: p.pointee)) }
        msgStart = 4
        msgEnd = offset
        readState = 1
    } else {
        msgStart = 0
        msgEnd = offset
    }

    var fullMessageRead = false

    if readState == 1 {
        let payloadRead = msgEnd - msgStart
        if payloadRead <= missingMsgBytes {
            assert(msgEnd > msgStart, "msgEnd (\(msgEnd) <= msgStart \(msgStart). This should not happen")
            if msgEnd > msgStart {
                msg += String(data: buffer[msgStart..<msgEnd], encoding: .utf8)!
                missingMsgBytes -= payloadRead
                offset = 0
            }
            fullMessageRead = (missingMsgBytes == 0)
        } else { // read more than was missing
            msg += String(data: buffer[msgStart..<msgStart+missingMsgBytes], encoding: .utf8)!
            fullMessageRead = true
            buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
                _ = memmove(p, p + missingMsgBytes, read - missingMsgBytes) // dst, src, number
            }
            offset = read-missingMsgBytes
        }
    }

    if fullMessageRead {
        handleMessage(msg)
        readState = 0
         msg = ""
        missingMsgBytes = 0
    }
}

此解决方案能够读取任意大小的消息。缓冲区大小只决定一次可以读取多少 => 缓冲区越大,应用程序越快。

我现在测试了大约一个小时的代码,它没有崩溃。旧代码在 1-2 分钟后崩溃。它现在似乎终于可以工作了。

但是由于我想提高我的编程知识,所以我想问一下我的代码中是否有一些不必要的复杂内容,或者是否有人看到可能仍然导致应用程序崩溃或读取错误数据的错误?

【讨论】:

  • 我可以发现一个问题:如果读取了部分消息并且末尾的 UTF-8 序列不完整,那么String(data: buffer[msgStart..&lt;msgEnd], encoding: .utf8)! 会崩溃。 ——这不是不可能解决的,而是很麻烦的。 (这就是为什么我选择“简单”的方法将所有内容存储在读取缓冲区中,直到消息完成。)
  • 在您撰写此评论的那一刻,应用程序就在该行崩溃了。 :-D
  • 因此,如果我希望能够读取任意长度的消息,我必须手动检查末尾的字符是否是有效的 UTF8,如果不是,则将末尾向左移动,直到我只有有效的人物?似乎很棘手,而且不太容易测试:(顺便说一句:测试所有网络内容的最佳方法是什么?目前我只是运行应用程序并等待崩溃,但我认为可能有更好的方法.
  • 我有另一个想法。从一个大小为 n 的缓冲区开始,每次它变小,增加它以使大小 >= messageLength 怎么样?我的意思是,每次将部分消息附加到 msg 内部应该大致相同吗?
  • 应该可以。您可以简单地将buffer.count 设置为更大的值,即附加零字节。 – 您可能仍想检查最大值,否则如果有坏数据进入,系统可能会尝试分配许多 GB。
猜你喜欢
  • 2011-11-22
  • 1970-01-01
  • 2021-04-09
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
相关资源
最近更新 更多