2

我尝试使用以下代码创建一个成帧器,它将 ASCII 字节流拆分为由管道 ascii 字符分隔的帧:"|".

import Network

fileprivate let pipe = Character("|").asciiValue!

class PipeFramer: NWProtocolFramerImplementation {
    static let label = "Pipe framer"
    static let definition = NWProtocolFramer.Definition(implementation: PipeFramer.self)

    var minLengthUntilNextMessage = 1 {
        didSet { print("client: minLength set to", minLengthUntilNextMessage) }
    }

    required init(framer: NWProtocolFramer.Instance) {}

    func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready }

    func handleInput(framer: NWProtocolFramer.Instance) -> Int {
        while true {
            var delimiterPosition: Int?
            _ = framer.parseInput(minimumIncompleteLength: minLengthUntilNextMessage, maximumLength: 65535) { buffer, endOfMessage in
                if let buffer = buffer {
                    print("client: parsing buffer: \"\(String(bytes: buffer, encoding: .utf8) ?? buffer.debugDescription)\"")
                    if let indexOfDelimiter = buffer.firstIndex(of: pipe) {
                        minLengthUntilNextMessage = 1
                        delimiterPosition = indexOfDelimiter
                    } else {
                        minLengthUntilNextMessage = buffer.count + 1
                    }
                } else {
                    print("client: no buffer")
                }
                return 0
            }

            if let length = delimiterPosition {
                guard framer.deliverInputNoCopy(length: length, message: .init(instance: framer), isComplete: true) else {
                    return 0
                }
                _ = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 65535) { _,_ in 1 }
            } else {
                return minLengthUntilNextMessage
            }
        }
    }

    func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
        try! framer.writeOutputNoCopy(length: messageLength)
        framer.writeOutput(data: [pipe])
    }

    func wakeup(framer: NWProtocolFramer.Instance) {}

    func stop(framer: NWProtocolFramer.Instance) -> Bool { return true }

    func cleanup(framer: NWProtocolFramer.Instance) { }
}

问题是,从我得到一个不以 结尾的块的那一刻起"|",成帧器就会卡在那个块上。因此,在这个不完整块之后的其他块永远不会完全到达framer.parseInput(...)调用中。因为它总是解析块,minimumIncompleteLength因此永远不会到达下一个点"|"

下面是这个问题的简单重现:

  1. 创建 TCP 服务器
  2. 设置服务器,以便在客户端连接时发送消息块。
  3. 使用上面的成帧器连接到服务器(在 1. 中创建)。
  4. 开始接收消息。

SWIFT代码:

import Network

let client = DispatchQueue(label: "Server")
let server = DispatchQueue(label: "Client")
let networkParameters = NWParameters.tcp
networkParameters.defaultProtocolStack.applicationProtocols.insert(NWProtocolFramer.Options(definition: PipeFramer.definition), at: 0)
let server = try! NWListener(using: .tcp)

server.newConnectionHandler = { connection in
    print("server: new connection from", connection.endpoint)

    print("server (client \(connection.endpoint)): state", connection.state)

    connection.viabilityUpdateHandler = { viable in
        print("server (client \(connection.endpoint)): state", connection.state)
        if viable {
            print("server: sending")
            connection.send(content: "A|Be||Sea".data(using: .utf8)!, isComplete: false, completion: .idempotent)

            serverQueue.asyncAfter(deadline: .now() + 5) {
                print("server: sending second part")
                connection.send(content: " is longer than expected|0|".data(using: .utf8)!, isComplete: true, completion: .idempotent)
            }
            serverQueue.asyncAfter(deadline: .now() + 8) {
                print("server: sending last part")
                connection.send(content: "Done|".data(using: .utf8)!, isComplete: true, completion: .idempotent)
            }
        }
    }

    connection.start(queue: serverQueue)
}

server.stateUpdateHandler = { state in
    print("server:", state)
    if state == .ready, let port = server.port {
        print("server: listening on", port)
    }
}

server.start(queue: serverQueue)

let client = NWConnection(to: .hostPort(host: "localhost", port: server.port!), using: networkParameters)

func receiveNext() {
    client.receiveMessage { (data, context, complete, error) in
        let content: String
        if let data = data {
            content = String(data: data, encoding: .utf8) ?? data.description
        } else {
            content = data?.debugDescription ?? "<no data>"
        }
        print("client: received \"\(content)\"", context.debugDescription, complete, error?.localizedDescription ?? "No error")

        receiveNext()
    }
}

client.stateUpdateHandler = { state in
    print("client:", state)

    if state == .ready {
        print("client: receiving")
        receiveNext()
    }
}

client.start(queue: clientQueue)

结果是:

server: waiting(POSIXErrorCode: Network is down)
server: ready
server: listening on 54894
client: preparing
client: ready
client: receiving
server: new connection from ::1.53179
server (client ::1.53179): state setup
server (client ::1.53179): state ready
server: sending
client: parsing buffer: "A|Be||Sea"
client: minLength set to 1
client: parsing buffer: "Be||Sea"
client: minLength set to 1
client: parsing buffer: "|Sea"
client: minLength set to 1
client: parsing buffer: "Sea"
client: minLength set to 4
client: parsing buffer: ""
client: minLength set to 1
client: received "A" Optional(Network.NWConnection.ContentContext) true No error
client: received "Be" Optional(Network.NWConnection.ContentContext) true No error
client: received "<no data>" Optional(Network.NWConnection.ContentContext) true No error
client: parsing buffer: "Sea"
client: minLength set to 4
server: sending second part
client: parsing buffer: "Sea "
client: minLength set to 5
client: parsing buffer: "Sea i"
client: minLength set to 6
server: sending last part
client: parsing buffer: "Sea is"
client: minLength set to 7
client: parsing buffer: "Sea is "
client: minLength set to 8

请注意,客户端永远不会收到第四条和第五条消息。我应该如何编写 Framer 以便在传入的不完整块之后接收消息?


参考

4

1 回答 1

1

我遇到了完全相同的问题......我正在使用的网络协议也有一个简单的分隔符来分隔每个“消息”,并且该协议没有告诉我会发生什么的标题。通常在缓冲区的末尾,只有部分消息没有分隔符,需要读取更多字节才能获取消息的其余部分。像这样的东西:

|              PACKET A              |              PACKET B             |
|<message>|<message>|<message><mess...age>|<message><message><message><m...essage>
     1         2         4      5a    5b      6        7        8      9a    9b

Note:
delimiter = | - single character
lhsMessage = message 5a 
rhsMessage = message 5b

即使在观看了 WWDC 并查看了 Apple 的其他示例之后,我仍然不完全理解handleInputparseInput应该如何工作。

我假设我可以简单地使用 ( ) 从句柄lhsMessage.count + 1输入返回,它会将部分消息保留在当前缓冲区中,并将额外的字节添加到 parseInput 可以检查的缓冲区(即来自 PACKET B)中。

但是,它似乎确实以这种方式工作。相反,我最终将lhsMessage的值存储在类 var 中,然后lhsMessage.countparseInput返回,我相信这会将缓冲区中的“光标”移动到结束并强制 handleInput 获取新数据包(即数据包 B)。

作为parseInput的一部分,然后我检查是否有 a lhsMessage,然后假设我是否找到了它实际上是的分隔符rhsMessage。然后我加入 LHS 和 RHS 创建一个completeMessage. 此时,我也从parseInput返回( rhsMessage.count + 1) 的值以再次移动光标。

现在要发送这个completeMessage我不能使用deliverInputNoCopy,因为组成completeMessage的字节不再在缓冲区中了:-)

相反,handleInput 使用deliverInput发回了消息。

于 2020-10-16T03:37:20.837 回答