【问题标题】:Correct way to use NWConnection for long-running TCP socket将 NWConnection 用于长时间运行的 TCP 套接字的正确方法
【发布时间】:2019-03-04 04:12:10
【问题描述】:

我整天都在与 NWConnection 斗争,以便在长时间运行的 TCP 套接字上接收数据。由于缺乏文档,在对自己造成以下错误后,我终于让它工作了:

  1. 数据不完整(由于只调用receive一次)
  2. 无序获取 TCP 数据(由于从计时器“轮询”接收...导致多个同时关闭等待获取数据)。
  3. 遭受无限循环(由于在接收后重新启动接收而没有检查“isComplete”布尔值——一旦套接字从另一端终止,这是....糟糕......非常糟糕)。

我所学的总结:

  1. 一旦您处于 .ready 状态,您就可以调用 receive...一次且仅一次
  2. 收到一些数据后,您可以再次调用 receive...但前提是您仍处于 .ready 状态并且 isComplete 为 false。

这是我的代码。我认为这是对的。但如果有错误请告诉我:

    queue = DispatchQueue(label: "hostname", attributes: .concurrent)
    let serverEndpoint = NWEndpoint.Host(hostname)
    guard let portEndpoint = NWEndpoint.Port(rawValue: port) else { return nil }
    connection = NWConnection(host: serverEndpoint, port: portEndpoint, using: .tcp)
    connection.stateUpdateHandler = { [weak self] (newState) in
        switch newState {
        case .ready:
            debugPrint("TcpReader.ready to send")
            self?.receive()
        case .failed(let error):
            debugPrint("TcpReader.client failed with error \(error)")
        case .setup:
            debugPrint("TcpReader.setup")
        case .waiting(_):
            debugPrint("TcpReader.waiting")
        case .preparing:
            debugPrint("TcpReader.preparing")
        case .cancelled:
            debugPrint("TcpReader.cancelled")
        }
    }

func receive() {  
    connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in
        debugPrint("\(Date()) TcpReader: got a message \(String(describing: content?.count)) bytes")
        if let content = content {
            self.delegate.gotData(data: content, from: self.hostname, port: self.port)
        }
        if self.connection.state == .ready && isComplete == false {
            self.receive()
        }
    }
}

【问题讨论】:

  • 我希望今天早上能找到这篇文章。我正在努力解决如果我使用 connection.send 发送多个数据位然后连接接收将数据组合在一起的问题。我应该将其视为仅在网络上发生的事情,还是应该限制我的发送,还是应该以不同的方式发送?
  • 我无法回答这个问题,但这听起来是一个很好的问题,尤其是在您包含代码的情况下。我希望看到更多 NWconnection 代码示例。
  • 所以,这表明我有点以错误的方式使用它(AFAIK)。我把连接看作是你打开的管道,然后不断地把东西放进去。当我将其视为 NWConnection 是用于发送单个事物并在该单个事物之后关闭时,一切都开始正常工作。
  • 如果要多次连接可以处理newConnectionHandler,在服务器上重启NWListener和NWConnection。
  • 不需要定时器。您应该处理NWConnection.receiveMessage 来获取消息并调用receiveNextMessage() 来获取下一个。

标签: swift networking


【解决方案1】:

我认为您可以多次使用短时间连接。例如,客户端连接到主机并要求主机做某事,然后告诉主机关闭连接。主机切换到等待模式以准备新的连接。见下图。

当客户端在特定时间内没有向主机发送关闭连接或应答事件时,您应该有连接计时器来关闭打开的连接。

【讨论】:

    【解决方案2】:

    在一个长时间运行的 TCP 套接字上,你应该实现自定义的心跳来监控连接状态是活动的还是断开的。

    心跳可以作为消息或加密数据发送,通常根据服务器规范文件来实现。

    下面作为示例概念代码来解释流程以供参考(没有网络数据包内容处理程序)。

    我不能保证这种方法是常见且正确的,但这对我的项目有效。

    import Network
    
    class NetworkService {
    
        lazy var heartbeatTimeoutTask: DispatchWorkItem = {
            return DispatchWorkItem { self.handleHeartbeatTimeOut() }
        }()
    
        lazy var connection: NWConnection = {
            // Create the connection
            let connection = NWConnection(host: "x.x.x.x", port: 1234, using: self.parames)
            connection.stateUpdateHandler = self.listenStateUpdate(to:)
            return connection
        }()
        
        lazy var parames: NWParameters = {
            let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
            if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
                isOption.version = .v4
            }
            parames.preferNoProxies = true
            parames.expiredDNSBehavior = .allow
            parames.multipathServiceType = .interactive
            parames.serviceClass = .background
            return parames
        }()
        
        lazy var tcpOptions: NWProtocolTCP.Options = {
            let options = NWProtocolTCP.Options()
            options.enableFastOpen = true // Enable TCP Fast Open (TFO)
            options.connectionTimeout = 5 // connection timed out
            return options
        }()
        
        let queue = DispatchQueue(label: "hostname", attributes: .concurrent)
        
        private func listenStateUpdate(to state: NWConnection.State) {
            // Set the state update handler
            switch state {
            case .setup:
                // init state
                debugPrint("The connection has been initialized but not started.")
            case .waiting(let error):
                debugPrint("The connection is waiting for a network path change with: \(error)")
                self.disconnect()
            case .preparing:
                debugPrint("The connection in the process of being established.")
            case .ready:
                // Handle connection established
                // this means that the handshake is finished
                debugPrint("The connection is established, and ready to send and receive data.")
                self.receiveData()
                self.sendHeartbeat()
            case .failed(let error):
                debugPrint("The connection has disconnected or encountered an: \(error)")
                self.disconnect()
            case .cancelled:
                debugPrint("The connection has been canceled.")
            default:
                break
            }
        }
        
        // MARK: - Socket I/O
        func connect() {
            // Start the connection
            self.connection.start(queue: self.queue)
        }
        
        func disconnect() {
            // Stop the connection
            self.connection.stateUpdateHandler = nil
            self.connection.cancel()
        }
        
        private func sendPacket() {
            var packet: Data? // do something for heartbeat packet
            self.connection.send(content: packet, completion: .contentProcessed({ (error) in
                if let err = error {
                    // Handle error in sending
                    debugPrint("encounter an error with: \(err) after send Packet")
                } else {
                    // Send has been processed
                }
            }))
        }
        
        private func receiveData() {
            self.connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] (data, context, isComplete, error) in
                guard let weakSelf = self else { return }
                if weakSelf.connection.state == .ready && isComplete == false, var data = data, !data.isEmpty {
                    // do something for detect heart packet
                    weakSelf.parseHeartBeat(&data)
                }
            }
        }
        
        // MARK: - Heartbeat
        private func sendHeartbeat() {
            // sendHeartbeatPacket
            self.sendPacket()
            // trigger timeout mission if the server no response corresponding packet within 5 second
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 5.0, execute: self.heartbeatTimeoutTask)
        }
        
        private func handleHeartbeatTimeOut() {
            // this's sample time out mission, you can customize this chunk
            self.heartbeatTimeoutTask.cancel()
            self.disconnect()
        }
        
        private func parseHeartBeat(_ heartbeatData: inout Data) {
            // do something for parse heartbeat
            
            // cancel heartbeat timeout after parse packet success
            self.heartbeatTimeoutTask.cancel()
            
            // send heartbeat for monitor server after computing 15 second
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15.0) {
                self.sendHeartbeat()
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-10
      • 1970-01-01
      • 2011-01-19
      • 2011-02-05
      • 2013-08-17
      • 1970-01-01
      • 2012-01-06
      • 2014-04-14
      相关资源
      最近更新 更多