Swift NWListener 监听udp 只能监听一次解决方法
这个功能应该是整个 app 的开端,但是也同时困扰了我好久。一度让我有些许的崩溃!
最开始想要去找一个三方库然后引入到项目之中,但是!!!刚接触ios开发的我并不是很熟悉,最开始甚至不知道有 pod 这么方便的东西存在。
所以一顿折腾度娘之后找到了苹果官方的udp连接类 :NWConnection
最开始只是简单的连接对面大佬的udp,代码我粘在下面了(很简单不做过多的讲解):
// serviceIP 为对方udp的ip , host 为对方udp监听的端口号
connection = NWConnection(host: serviceIP, port: port, using: .udp); connection!.start(queue: .global()) let contentToSend="hello world".data(using: String.Encoding.utf8) connection!.send(content: contentToSend, 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!) ") } }))
发送很简单,但是到了监听差点没难死我(我严重怀疑这是苹果的bug)
监听的时候用 NWListener 类
这也是不少人选择的一个监听类,从类的名字就可以看出来这货就是专业监听户,最开始的代码我放在下面了(不要急着扒,这个代码是有问题的!):
func initUdpListen() -> Void { do{ try listen = NWListener(using: .udp, on: listenPort) listen?.stateUpdateHandler = { [self](newState) in switch newState { case .failed: startUdpListen() default: break } } listen?.newConnectionHandler = {(newConnection) in newConnection.stateUpdateHandler = { [self]newState in switch newState { case .ready: receiveUDP(on: newConnection)case .failed: startUdpListen() default: break } } newConnection.start(queue: DispatchQueue(label: "UDP Server Queue")) } } catch { print("unable to create listener") } listen?.start(queue: .main) } func receiveUDP(on listenConnect: NWConnection){ listenConnect.receiveMessage { (data, context, isComplete, error) in if let error = error { print("udp",error) return } if let data = data, !data.isEmpty { let backToString = String(decoding: data, as: UTF8.self) print("\(backToString)") }
receiveMessageUDP(on: listenConnect) } }
网上好多人都是这么写的,看上去没有什么问题
但是!!!
当你使用 udp 单播的时候是没有问题,一旦使用组播,那就神奇了,你只能接收到一次,之后整个udp就是不好使的了!
各种去查询,但是万万没有想到,我的天啊。这个问题好像是无解,而当我看到苹果官方的文档之后我一度崩溃
可能是本身这个 newConnectionHandle 就是有问题的,所以苹果官方在最新的bate版本中新加入了 newConnectionGroupHandle 。但是我这个项目不可能等 bate 之后再继续呀,所以我想了一个比较狗的方法!它不是还好使一次呢么,那我就接收一次就让它重新初始化一个呗。于是有了并不优雅的改进版:
var listen :NWListener? = nil
func initUdpListen() -> Void {
do{ try listen = NWListener(using: .udp, on: listenPort) listen?.stateUpdateHandler = { [self](newState) in switch newState { case .failed: startUdpListen() default: break } } listen?.newConnectionHandler = {(newConnection) in newConnection.stateUpdateHandler = { [self]newState in switch newState { case .ready: receiveUDP(on: newConnection) case .cancelled: listen = nil print("udp listen","udp cancelled") initUdpListen() case .failed: startUdpListen() default: break } } newConnection.start(queue: DispatchQueue(label: "UDP Server Queue")) } } catch { print("unable to create listener") } listen?.start(queue: .main) } func receiveUDP(on listenConnect: NWConnection){ listenConnect.receiveMessage { (data, context, isComplete, error) in if let error = error { print("udp",error) return } if let data = data, !data.isEmpty { let backToString = String(decoding: data, as: UTF8.self) print("\(backToString)") } listenConnect.cancel() self.listen?.cancel() } }
当 receiveUDP 函数执行之后,我就让 listen 直接注销,然后再 listen 的 stateUpdateHandler 的 .cancel 状态直接重新初始化整个 NWListener (上一个忘记写 listen 的初始化定义了)
这方法虽然糙了点,但是好歹功能是可以实现了。现在就期待着新 swift 能把这个神奇的问题改掉吧!
最后在结尾插一个 iOS 获取本机局域网 IP 的方法
//获取本机ip func getLocalIPAddressForLAN() -> String? { var address: String? // get list of all interfaces on the local machine var ifaddr: UnsafeMutablePointer? = nil guard getifaddrs(&ifaddr) == 0 else { return nil } guard let firstAddr = ifaddr else { return nil } for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { let interface = ifptr.pointee // Check for IPV4 or IPV6 interface let addrFamily = interface.ifa_addr.pointee.sa_family if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { // Check interface name let name = String(cString: interface.ifa_name) if name == "en0" { // Convert interface address to a human readable string var addr = interface.ifa_addr.pointee var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST)) getnameinfo(&addr, socklen_t(interface.ifa_addr.pointee.sa_len), &hostName, socklen_t(hostName.count), nil, socklen_t(0), NI_NUMERICHOST) address = String(cString: hostName) } } } freeifaddrs(ifaddr) return address }
亲测没有毛病!