iOS 14 上的 UDP 侦听器

UDP Listener on iOS 14

我有一个关于如何在 iOS 14 上设置 UDP 侦听器的问题。我有一个 UDP 侦听器,它过去一直有效,但在更新到 iOS 14 后它可以工作 sporadically/not 完全没有。

它存在于 NSObject 中,并在端口 15000(无特定 IP 地址)上侦听跨本地网络的 UDP 广播。它使用 CocoaAsyncSocket 库。当我调用时 setUpSocket() 没有触发本地网络权限,但该应用程序能够偶尔接收 UDP 数据包。


var socket: GCDAsyncUdpSocket?
var broadcastPort: UInt16 = 15000
var broadcastAddress: String = ""
var connectAddress = ""
var connectPort = 0

func setUpSocket() {
    findUDP()
    let socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
     
    socket.setIPv4Enabled(true)
    socket.setIPv6Enabled(false)
     
    do {
      try socket.bind(toPort: broadcastPort) /*15000*/
      try socket.enableBroadcast(false)
      try socket.beginReceiving()
       
    } catch let error as NSError {
       
      print("Issue with setting up listener \(error)")
       
    }
     
  }

/*Called when UDP packets are received.*/
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress: Data, withFilterContext filterContext: Any?) {
     
    do {
      let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
       
      if (connected == false) {
        if (jsonDictionary["Addresses"] != nil) {
          if (jsonDictionary["Addresses"] is NSArray) {
            let addresses = jsonDictionary["Addresses"] as! NSArray
             
            for i in addresses {
              let ipAddress:String = i as! String
              if (ipAddress.range(of: "^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$", options: .regularExpression) != nil) {
                connectAddress = ipAddress
              }
            }
            connectPort = jsonDictionary["Port"] as! Int
          }
           
          /*Sets up a TCP connection on the IP and Port provided in the UDP broadcast.*/
          setupNetworkCommunication(ip: connectAddress, port: connectPort)
          
          closeSocket()

        }
      }
       
    } catch let error {
      return print(error)
    }
  }

我如何更新它以符合 iOS 14?如果我需要更新以使用 Bonjour 服务,我如何在不指定地址的情况下监听端口(并且不必查找特定的 Bonjour 服务广播,因为我正在查找的广播不使用 Bonjour)。

是否可以快速打开和关闭 Bonjour NWBrowser 以触发网络权限,然后按原样使用我的代码?这似乎可行,但充其量似乎很老套。

提前致谢。

我能够进一步探索这个问题,并通过苹果开发者论坛获得了一些帮助,在这里也为感兴趣的人发布了一个答案。

我最终使用 NWListener 来侦听 UDP 数据包,然后在我收到一些东西后设置一个 NWConnection。我使用这个 NWConnection 从 UDP 广播中读取数据。

来自奎因“爱斯基摩人”:

Listening for UDP broadcasts via an NWListener and then using the NWConnection objects it vends (via the new connection handler) to communicate over unicast with the broadcast’s sender is an expected use case.

我鼓励阅读本文的任何人也查看 our discussion on the Apple Developer Forum

这是我的实现:

  var udpListener: NWListener?
  var udpConnection: NWConnection?
  var backgroundQueueUdpListener  = DispatchQueue.main
   
  func findUDP() {
    let params = NWParameters.udp
    udpListener = try? NWListener(using: params, on: 15000)
     
    udpListener?.service = NWListener.Service.init(type: "_appname._udp")
     
    self.udpListener?.stateUpdateHandler = { update in
      print("update")
      print(update)
      switch update {
      case .failed:
        print("failed")
      default:
        print("default update")
      }
    }
    self.udpListener?.newConnectionHandler = { connection in
      print("connection")
      print(connection)
      self.createConnection(connection: connection)
      self.udpListener?.cancel()
    }
    udpListener?.start(queue: self.backgroundQueueUdpListener)
  }
   
  func createConnection(connection: NWConnection) {
    self.udpConnection = connection
      self.udpConnection?.stateUpdateHandler = { (newState) in
        switch (newState) {
        case .ready:
          print("ready")
          self.send()
          self.receive()
        case .setup:
          print("setup")
        case .cancelled:
          print("cancelled")
        case .preparing:
          print("Preparing")
        default:
          print("waiting or failed")
        }
      }
      self.udpConnection?.start(queue: .global())
  }
   
  func endConnection() {
    self.udpConnection?.cancel()
  }

为了在我们的应用程序中使用 CocoaAsyncSocket 和 UDP,我们必须完成以下步骤:

  1. 向 Apple 申请多播授权(申请者必须是团队的 Account Holder):com.apple.developer.networking.multicast

  2. 在目标的 Info.plist 中,为以下键设置一个字符串:

    Privacy - Local Network Usage Description

  3. 从 Apple 获得授权许可后,为您的应用程序的 *.entitlements 文件中的以下键添加一个 true(1) 布尔值(这最后一步让我们来自接收 UDP 广播数据包):

    com.apple.developer.networking.multicast