Trouble with sending data using Apple's Network framework to server. TCP / CLIENT / SERVER

Hi all,

I'm just a beginner wanting to create a client on MacOs for controlling a server(Blackmagic Hyperdeck). After a lot of beginners tutorials about basic Swift and SwiftUI, i started working on the client app. It should be quite basic: Open connection to server via TCP, and once a connection is established send easy text data to the server.

After searching a lot, I figured Apple's Network framework (NWframework) would be quite useful. I found some examples and with those examples, i compiled the code below.

Now when running the application, I'm able to setup a connection and send a first datastring to the server by calling the initClient function. But then i try to send another string of data by calling functions as stopPlayout or sendData, but nothing happens. When adding client.start() before the client.connection.send() in the functions, it opens a new connection to the server and sends the string. But i want the client to send the data over the first connection which is still open. Hyperdeck only allows 1 connection.

Anyone can see what I'm doing wrong here?
Thanks a lot!

Client.swift:

import Foundation
import Network

@available(macOS 10.14, *)
class Client {
let connection: ClientConnection
let host: NWEndpoint.Host
let port: NWEndpoint.Port

init(host: String, port: UInt16) {
    self.host = NWEndpoint.Host(host)
    self.port = NWEndpoint.Port(rawValue: port)!
    let nwConnection = NWConnection(host: self.host, port: self.port, using: .tcp)
    connection = ClientConnection(nwConnection: nwConnection)
}

func start() {
    print("Client started \(host) \(port)")
    connection.didStopCallback = didStopCallback(error:)
    connection.start()
}

func stop() {
    connection.stop()
}

func send(data: Data) {
    connection.send(data: data)
}

func didStopCallback(error: Error?) {
    if error == nil {
        exit(EXIT_SUCCESS)
    } else {
        exit(EXIT_FAILURE)
    }
}
}

ClientConnection.swift:

import Foundation
import Network

@available(macOS 10.14, *)
class ClientConnection {

let  nwConnection: NWConnection
let queue = DispatchQueue(label: "Client connection Q")

init(nwConnection: NWConnection) {
    self.nwConnection = nwConnection
}

var didStopCallback: ((Error?) -> Void)? = nil

func start() {
    print("connection will start")
    nwConnection.stateUpdateHandler = stateDidChange(to:)
    setupReceive()
    nwConnection.start(queue: queue)
}

private func stateDidChange(to state: NWConnection.State) {
    switch state {
    case .waiting(let error):
        connectionDidFail(error: error)
    case .ready:
        print("Client connection ready")
    case .failed(let error):
        connectionDidFail(error: error)
    default:
        break
    }
}

private func setupReceive() {
    nwConnection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, _, isComplete, error) in
        if let data = data, !data.isEmpty {
            let message = String(data: data, encoding: .utf8)
            print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
        }
        if isComplete {
            self.connectionDidEnd()
        } else if let error = error {
            self.connectionDidFail(error: error)
        } else {
            self.setupReceive()
        }
    }
}

func send(data: Data) {
    nwConnection.send(content: data, completion: .contentProcessed( { error in
        if let error = error {
            self.connectionDidFail(error: error)
            return
        }
            print("connection did send, data: \(data as NSData)")
    }))
}

func stop() {
    print("connection will stop")
    stop(error: nil)
}

private func connectionDidFail(error: Error) {
    print("connection did fail, error: \(error)")
    self.stop(error: error)
}

private func connectionDidEnd() {
    print("connection did end")
    self.stop(error: nil)
}

private func stop(error: Error?) {
    self.nwConnection.stateUpdateHandler = nil
    self.nwConnection.cancel()
    if let didStopCallback = self.didStopCallback {
        self.didStopCallback = nil
        didStopCallback(error)
    }
}
}

ContentView.swift:

import Foundation
import SwiftUI
import Network

    
struct ContentView: View {
    
    
    @State var ipAdresHyperdeck1: String = "localhost"
    @State private var commandValue: String = ""
    @State var dataValue: String = ""
    let portHyperdeck: UInt16 = 8888
    
    var body: some View {
        VStack {
            TabView() {
                VStack {
                    HStack {
                        Text("IP Hyperdeck 1")
                            .padding(.leading, 60.0)
                        TextField("IP adres", text: $ipAdresHyperdeck1)
                            .frame(width: 90.0)
                    }
                    HStack {
                        Button(action: {
                            self.initClient(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
                        }) {
                            Text("     Connect Hyperdeck 1     ")}
                            .padding(.leading, 60)
                    Spacer()
                        Button(action: {
                            self.stopConnection(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
                        }) {
                            Text("    Stop hyperdeck 1    ")}
                            .padding(.trailing, 55)
                    }
                }
                    
                    .tabItem { Text("Connect").tag(1) }
                VStack {
                    Button(action: {
                        self.stopPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
                    }) {
                    Text("STOP")
                    }
                    Button(action: {
                        self.playPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
                    }) {
                    Text("PLAY")
                    }
                    Button(action: {
                        self.goToInPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
                    }) {
                    Text("GO TO IN")
                    }
                }
                        .tabItem { Text("Playout").tag(2) }
                    Text("Tab Content 3").tabItem { Text("Record").tag(3) }
            VStack {
                HStack {
                    Text("IP Adres Hyperdeck 1:")
                    Text("\(ipAdresHyperdeck1)")
                }
            }
                .tabItem { Text("Status").tag(4) }
                HStack {
            Text("Data")
                    TextField("Data you want to send", text: $commandValue)
            Button(action: {
                self.sendData(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
            }) {
            Text("    Send Data    ")}
                }
            .tabItem { Text("Send data").tag(5) }
                }
            .padding(.top, 20)
            .frame(width: 500.0, height: 400.0)
            
        }
    }
   
    func initClient(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        let welcomecommand = String("Hi server!")
            client.start()
            client.connection.send(data: (welcomecommand.data(using: .utf8))!)
        }

    func stopConnection(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        client.stop()
    }
        
    func sendData(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        let command = String("\(commandValue)")
            client.connection.send(data: (command.data(using: .utf8))!)
    }
    
    func stopPlayout(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        let command = String("stop")
            client.connection.send(data: (command.data(using: .utf8))!)
    }
    
    func playPlayout(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        let command = String("play")
            client.connection.send(data: (command.data(using: .utf8))!)
    }
    
    func goToInPlayout(server: String, port: UInt16) {
        let client = Client(host: server, port: port)
        let command = String("goto: clip: start")
            client.connection.send(data: (command.data(using: .utf8))!)
    }
    

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
}
1 Like

First up, I’m going to recommend that you pop on over to DevForums, and specifically the Core OS > Networking, because this stuff is more about Apple APIs than about Swift.

Secondly, just so that this post isn’t entirely content free, my initial debugging step in situations like this is to use a packet trace to see what’s happening on the ‘wire’. That let’s you double check your assumptions about what is or isn’t being sent, the connection lifecycle, and so on. See Recording a Packet Trace.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Hi Quinn,

Will do, thanks!
https://forums.developer.apple.com/message/410281#410281