Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

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