简体   繁体   中英

NWConnection, 100% CPU usage and crash after sending 512 UDP packets

This is my first time writing Swift and using Xcode, so please bear with me.

I'm trying to write an iPhone app which takes values from the device's gyroscope/magnetometer and sends them in UDP packets to a specific IP address and port, at a specific rate (1-100Hz). This way, I can strap my phone to my head and use it as 'free' TrackIR alternative.

Opentrack , the software I'm using to receive the UDP packets, expects 48 bytes. 6 8-byte doubles in this order: X, Y, Z, Yaw, Pitch, Roll. Since I can't measure the phone's position relative to the display, the first 3 doubles will be 0. While Yaw, Pitch and Roll will be the phone's attitude in degrees.

Here's a stripped down version of my code so far ( full file here ):

import UIKit
import CoreMotion
import Network

class ViewController: UIViewController {
    var enabled = false
    var rate: Int = 100
    var motion = CMMotionManager()
    var connection: NWConnection?

    @IBOutlet weak var activeState: UILabel!
    @IBOutlet weak var ipAddress: UITextField!
    @IBOutlet weak var port: UITextField!
    @IBOutlet weak var rateDisplay: UILabel!
    @IBOutlet weak var rateSlider: UISlider!
    
    func radToDegData(value: Double) -> Data {
        return withUnsafeBytes(of: value*180/Double.pi) { Data($0) }
    }
    
    func startStream(hostUDP: NWEndpoint.Host, portUDP: NWEndpoint.Port) {
        motion.deviceMotionUpdateInterval = 1 / Double(rate)
        motion.startDeviceMotionUpdates(to: OperationQueue.current!){ (data, error) in
            if let trueData = data{
                let UDPmessage = self.radToDegData(value: 0) + self.radToDegData(value: 0) + self.radToDegData(value: 0) + self.radToDegData(value: trueData.attitude.yaw) + self.radToDegData(value: trueData.attitude.pitch) + self.radToDegData(value: trueData.attitude.roll)
                self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
                self.sendUDP(UDPmessage)
                self.connection?.start(queue: .global())
            }
        }
    }
    
    func stopStream() {
        motion.stopDeviceMotionUpdates()
    }
    
    func sendUDP(_ content: Data) {
        self.connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
            if (NWError == nil) {
                //print("Data was sent to UDP")
            } else {
                //print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
            }
        })))
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func enableSwitch(_ sender: Any) {
        enabled.toggle()
        if enabled {
            startStream(hostUDP: .init(ipAddress.text!), portUDP: NWEndpoint.Port(rawValue: UInt16(port.text ?? "4242")!) ?? NWEndpoint.Port.any)
        } else {
            stopStream()
        }
    }
}

I'm having 2 problems:

  • Running this on a physical iPhone SE 2 at 100Hz, Xcode reports 100-101% CPU usage. I can't really imagine that this app is that demanding, even at 100Hz. Another odd thing is that while I'd expect 100% usage to mean that the speed is throttled down, I'm still receiving ~100 packets per second on a Python script on my laptop I wrote for testing.
  • No matter the rate, only 512 packets are sent. After that, 2 error messages are printed to the console for every attempt:

2020-12-13 22:03:44.765054+0100 UDPHeadTrack[5571:3061403] [] nw_path_evaluator_create_flow_inner NECP_CLIENT_ACTION_ADD_FLOW 31FA4B2D-1E8A-4D16-A1A6-D023471B59C0 [28: No space left on device]
2020-12-13 22:03:44.765089+0100 UDPHeadTrack[5571:3061403] [connection] nw_endpoint_flow_setup_channel [C513 192.168.0.1:4242 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, ipv6, dns)] failed to request add nexus flow

Since this is my first Swift project, a good chunk of it is copy-pasted examples/documentation, which I'm assuming is why I'm experiencing these problems. Any help would be appreciated.

You are creating a new NWConnection each time you receive a motion update. Your app can only have a limited number of open network connections (512 in this case). Once the quota is exceeded you get an error message.

A simple restructure is just to use an existing NWConnection if it exists:

func startStream(hostUDP: NWEndpoint.Host, portUDP: NWEndpoint.Port) {
    motion.deviceMotionUpdateInterval = 1 / Double(rate)
    motion.startDeviceMotionUpdates(to: OperationQueue.current!){ (data, error) in
        guard let trueData = data else {
            return
        }

        if self.connection == nil {
            self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
            self.connection?.start(queue: .global())
        }
        
        let udpMessage = self.radToDegData(value: 0) + self.radToDegData(value: 0) + self.radToDegData(value: 0) + self.radToDegData(value: trueData.attitude.yaw) + self.radToDegData(value: trueData.attitude.pitch) + self.radToDegData(value: trueData.attitude.roll)      
        self.sendUDP(udpMessage)
       
    }    
}

func stopStream() {
    motion.stopDeviceMotionUpdates()
    self.connection?.cancel()
    self.connection = nil

}

func sendUDP(_ content: Data) {
    guard let connection = self.connection else {
        return
    }
    connection.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (error) in
        if let error = error {
           print("ERROR! Error when data (Type: Data) sending. NWError: \n \(error)")
        } else { 
            print("Data was sent to UDP")
        }
    })))
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM