我尝试使用以下代码创建一个框架器,将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,因此永远不会到达下一个"|"所在的位置。
以下是这个问题的简单再现:
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,以便它在传入的不完整块之后接收消息?
参考文献
发布于 2020-10-16 03:37:20
我也有同样的问题..。我所使用的网络协议也有一个简单的分隔符来分隔每条“消息”,并且该协议没有告诉我期望什么的头。通常在缓冲区的末尾,只有一个没有分隔符的部分消息,需要读取更多的字节才能得到消息的其余部分。就像这样:
| 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和苹果的其他例子之后,我仍然不完全理解handleInput和parseInput应该如何发挥作用。
我假设我可以简单地从handleInput返回(lhsMessage.count + 1),它会将部分消息保存在当前的缓冲区中,并将额外的字节添加到缓冲区(即数据包B)中,parseInput可以检查该缓冲区。
然而,它似乎确实是这样运作的。相反,我最终将lhsMessage的值存储在一个类var中,然后从parseInput返回lhsMessage.count,我认为它会将缓冲区中的“游标”移到末尾,并强制handleInput获取新数据包(即数据包B)。
作为parseInput的一部分,我检查是否有lhsMessage,然后假设是否找到一个分隔符,即它实际上是rhsMessage。然后我加入LHS和RHS来创建一个completeMessage。此时,我还从parseInput返回(rhsMessage.count + 1)值,以再次移动光标。
现在要发送这个completeMessage,我不能使用completeMessage作为组成completeMessage的字节不再在缓冲区中:-)
相反,deliverInput.将消息发送回handleInput
https://stackoverflow.com/questions/64266869
复制相似问题