UDP Receive is not working

Hello everyone I'm new to swift and I can't quite figure it out yet:( I am developing a simple online game for mac os that involves two players connected to the same WIFI. I need to constantly receive information from the server and I don't understand how to implement it. If I call the receive function indefinitely, then my program freezes. I realized that this should happen asynchronously, but that's just how my program understands when a package came from the server. I understand that I need a delegate or handler, but I don't understand how to do it. Please help me to add the receive function and everything that is necessary for it

import Foundation
import Network

enum CustomErrors: Error {
    case DataError
    case NetworkError
    case DecoderError
    case InvalidAddress
}

class TapperConnection: ObservableObject {
    private var _serverAlive = false
    private var connection: NWConnection!
    private var serverPort: UInt16 = 20001
    private var serverIp: String = "127.0.0.1"
    private var _myDeviceName = Host.current().localizedName ?? ""

    @Published var messageDc: [HostData] = []
    @Published var messageLobby: [HostData] = []
    @Published var messageState: GameData = GameData()

    private var buffer = 2048
    private var _inputData = ""
    private var _outputData = ""

    private var _myIp = ""

    private var isServer = false
    private var isClient = false

    var myIp: String {
        return _myIp
    }

    var myDeviceName: String {
        return _myDeviceName
    }

    private func getMyIp() -> String? {
        var address: String?

        var ifaddr: UnsafeMutablePointer<ifaddrs>?
        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

            let addrFamily = interface.ifa_addr.pointee.sa_family
            if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
                let name = String(cString: interface.ifa_name)
                if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" {
                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    getnameinfo(interface.ifa_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
    }

    private func isValidIP(_ ip: String) -> Bool {
        let regex = try! NSRegularExpression(pattern: "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
        return regex.firstMatch(in: ip, range: NSRange(location: 0, length: ip.utf16.count)) != nil
    }

    @Sendable
    private func updateServerState(to state: NWConnection.State) {
        switch state {
        case .setup:
            _serverAlive = true
        case .waiting:
            _serverAlive = true
        case .ready:
            _serverAlive = true
        case .failed:
            _serverAlive = false
        case .cancelled:
            _serverAlive = false
        case .preparing:
            _serverAlive = false
        default:
            _serverAlive = false
        }
    }

    func createConnection() throws {
        let ip = getMyIp()
        if ip != nil {
            serverIp = ip!
            _myIp = ip!
        } else {
            throw CustomErrors.NetworkError
        }

        isServer = true

        do {
            try connectToServer()
        } catch {
            throw CustomErrors.NetworkError
        }
    }

    func createConnection(ip: String) throws {
        if isValidIP(ip) {
            serverIp = ip
        } else {
            throw CustomErrors.InvalidAddress
        }

        let _ip = getMyIp()
        if _ip != nil {
            _myIp = _ip!
        } else {
            throw CustomErrors.NetworkError
        }

        isClient = true

        do {
            try connectToServer()
        } catch {
            throw CustomErrors.NetworkError
        }
    }

    private func connectToServer() throws {
        if isServer {
            // ...............
            // run server exec
            // ...............
        }

        let _params = NWParameters(dtls: nil, udp: .init())
        _params.requiredLocalEndpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(_myIp), port: 20002)
        connection = NWConnection(host: NWEndpoint.Host(serverIp), port: NWEndpoint.Port(rawValue: serverPort)!, using: _params)
        connection.stateUpdateHandler = updateServerState(to:)
        connection.start(queue: .global())

        while !_serverAlive {}
        do {
            try send(message: "im:\(_myDeviceName)")
            receive()

        } catch {
            print("Error sending disconnect message: \(error)")
        }
    }

    func closeConnection() {
        do {
            try send(message: "dc:\(_myDeviceName)")
        } catch {
            print("Error sending disconnect message: \(error)")
        }
        _serverAlive = false
        connection.cancel()
    }

    func send(message: String) throws {
        var error = false
        connection.send(content: message.data(using: String.Encoding.utf8), completion: NWConnection.SendCompletion.contentProcessed(({ NWError in
            if NWError == nil {
                print("Data was sent!")
            } else {
                error = true
            }
        })))

        if error {
            throw CustomErrors.NetworkError
        }
    }

    func receive() {
        self.connection.receive(minimumIncompleteLength: 1, maximumLength: 65535) { data, _, isComplete, _ in
            if isComplete {
                if data != nil {
                    let response: String = String(decoding: data!, as: UTF8.self)
                    
                    var decodeData: Any
                    var messageType: MessageType
                    (decodeData, messageType) = try! Decoder.decodeMessage(response)
                        
                    switch messageType {
                    case MessageType.lobby:
                        self.messageLobby = decodeData as! [HostData]
                    case MessageType.state:
                        self.messageState = decodeData as! GameData
                    case MessageType.dc:
                        self.messageDc = decodeData as! [HostData]
                    }
                }
                self.receive()
            }
        }
    }
}
Answered by DTS Engineer in 808337022

Looking at your code I see that it’s trying to get the device’s IP address. That’s almost always a problem )-: In Extra-ordinary Networking I have a subpost entitled Don’t Try to Get the Device’s IP Address that has the whole backstory here.

I am developing a simple online game for mac os that involves two players connected to the same [Wi-Fi].

In that situation it’s best for your server to use Bonjour [1]:

  • On the server, advertise your listener using Bonjour. Specifically, set up the listener like this:

    let listener = try NWListener(using: .udp)
    listener.service = .init(type: "_mygame._udp")
    

    where _mygame is a service type for your app.

  • On the client, browse for listeners using NWBrowser. Give it the same service type as you used in the previous step.

  • When the user chooses a listener, connect to that using the endpoint returned by the browser.

This gets you out of the business of messing around with IP addresses, which is a good thing because IP addresses are super tricky to use correctly.

I have a simple example of this process in Getting Started with Bonjour. It uses TCP, but the basic process is the same for UDP.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Bonjour is an Apple term for three industry-standard protocols:

Looking at your code I see that it’s trying to get the device’s IP address. That’s almost always a problem )-: In Extra-ordinary Networking I have a subpost entitled Don’t Try to Get the Device’s IP Address that has the whole backstory here.

I am developing a simple online game for mac os that involves two players connected to the same [Wi-Fi].

In that situation it’s best for your server to use Bonjour [1]:

  • On the server, advertise your listener using Bonjour. Specifically, set up the listener like this:

    let listener = try NWListener(using: .udp)
    listener.service = .init(type: "_mygame._udp")
    

    where _mygame is a service type for your app.

  • On the client, browse for listeners using NWBrowser. Give it the same service type as you used in the previous step.

  • When the user chooses a listener, connect to that using the endpoint returned by the browser.

This gets you out of the business of messing around with IP addresses, which is a good thing because IP addresses are super tricky to use correctly.

I have a simple example of this process in Getting Started with Bonjour. It uses TCP, but the basic process is the same for UDP.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Bonjour is an Apple term for three industry-standard protocols:

UDP Receive is not working
 
 
Q