I'm a complete noob to networking but I need local discovery to share ARKit Worldmaps. The MCSession peer limit of 8 users isn't enough for me because I need unlimited peer connections (all peer to peer). I decided to use Bonjour and got a discovery system setup. Where I got stumped is actually connecting user to user and sending data back and forth. To do that I need to create sockets for inputstreams and outputstreams and somehow add to the them to the NetService Delegate method:
func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream stream: OutputStream) { }
All I know is I have to use CocoaAsyncSocket to make life easier but I'm still lost on what to do in the didAcceptConnectionWith and this is turning into a daunting task. In my reearch I came across the NWConnection class. From my understanding it's supposed to replace Bonjour but I don't have much reference on how to use it. For eg I followed this tutorial, this tutorial, and ths code from Eskimo but that's all that I could find.
There are 2 problems with NWConnection the first being unlike Bonjour which has delegate methods to find all local peers and to remove them:
// find
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { }
// remove
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { }
I cannot see how to do this with NWConnection. I did find this question where the op was trying to use the Bonjour service to discover address and then hook those into NWConnection as an endpoint but eskimo said not to do it that way. It does seem like a good idea to get around this issue I'm having.
The second problem is that I need the users of my app to discover all the other users around them. With the above Bonjour delgate method that is taken care. But with NWConnection it seems you have to set the endpoint in advance:
let connection = NWConnection(host: "example.com", port: 80, using: .tcp)
connection.stateUpdateHandler = self.stateDidChange(to:)
That's fine if you're connecting to a webpage but I don't see how one http endpoint correlates to local discovery for the users using my app.
Here is my Bonjour code below and how i use it in my ARKit VC
ARKitController:
import Network
class ARKitController: UIViewController {
lazy var sceneView: ARSCNView = {
let sceneView = ARSCNView()
sceneView.translatesAutoresizingMaskIntoConstraints = false
sceneView.delegate = self
return sceneView
}()
let configuration = ARWorldTrackingConfiguration()
var connection: NWConnection?
var netBrowser: NetBrowser!
var arrOfIPAddrresses = [String]()
func overrideViewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
netBrowser = NetBrowser()
netBrowser.delegate = self
netBrowser.startSearch()
sceneView.session.run(configuration, options: [])
}
func overrideViewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
netBrowser.stopDiscovery()
netBrowser = nil
sceneView.session.pause()
}
func saveIP(address: String) {
arrOfIPAddresses.append(address)
}
func remove(_ service: NetService) {
// get the ipaddress from the service then remove it from the array
}
func sendDataUsing(_ sockAddr: UnsafeBufferPointer ) {
// get host and port from socket address ...
let hostUDP = NEWEndpoint.Host ...
let portUDP = NWEndpoint.Post ...
connectToUDP(hostUDP, portUDP)
}
}
//MARK:- NWConnection Methods
extension ARKitController {
func connectToUDP(_ hostUDP: NWEndpoint.Host, _ portUDP: NWEndpoint.Port) {
self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
self.connection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("State: Ready\n")
self.sendThisUsersWorldMap()
self.receiveOtherUsersWorldMap()
case .setup:
print("State: Setup\n")
case .cancelled:
print("State: Cancelled\n")
case .preparing:
print("State: Preparing\n")
default:
print("ERROR! State not defined!\n")
}
}
self.connection?.start(queue: .global())
}
func sendThisUsersWorldMap() {
sceneView.session.getCurrentWorldMap { (worldMap, error) in
guard let map = worldMap else { print("Error: \(error!.localizedDescription)") return }
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true)
else { print("can't encode map") return }
self.connection?.send(content: data, 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!)")
}
})))
}
}
func receiveOtherUsersWorldMap() {
self.connection?.receiveMessage { (data, context, isComplete, error) in
if (isComplete) {
print("Receive is complete")
if let data = data {
self.mergeOtherUsersWorldMap(data)
}
}
}
}
}
Bonjour:
protocol NetBrowserDelegate: class {
func saveIP(address: String)
func remove(_ service: NetService)
func sendDataUsing(_ sockAddr: UnsafeBufferPointer)
}
class NetBrowser: NSObject, NetServiceBrowserDelegate, NetServiceDelegate {
var browser: NetServiceBrowser?
var services = [NetService]()
let domain = "local."
let name = "_http._tcp"
weak var delegate: NetBrowserDelegate?
func startSearch() {
services.removeAll()
browser = NetServiceBrowser()
browser?.includesPeerToPeer = true
browser?.delegate = self
browser?.stop()
browser?.schedule(in: RunLoop.current, forMode: .default)
browser?.searchForServices(ofType: self.name, inDomain: self.domain)
RunLoop.current.run()
}
func stopDiscovery() {
browser?.stop()
browser?.delegate = nil
browser = nil
}
}
// MARK:- Delegate Methods
extension NetBrowser {
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
print("found service")
services.append(service)
service.delegate = self
service.publish(options: NetService.Options.listenForConnections)
service.resolve(withTimeout: 5.0)
}
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
if let index = self.services.firstIndex(of:service) {
self.services.remove(at:index)
print("removing a service")
delegate?.remove(service)
}
}
func netServiceDidResolveAddress(_ sender: NetService) {
print("netServiceDidResolveAddress get called with \(sender).")
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
guard let data = sender.addresses?.first else {
print("guard let data failed")
return
}
data.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Void in
let sockaddrPtr = pointer.bindMemory(to: sockaddr.self)
guard let unsafePtr = sockaddrPtr.baseAddress else { return }
guard getnameinfo(unsafePtr, socklen_t(data.count), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
return
}
delegate?.sendDataUsing(sockaddrPtr)
}
let ipAddress = String(cString:hostname)
print(ipAddress)
delegate?.saveIP(address: ipAddress)
}
func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) {
print("netServiceDidNotResolve:\(sender)");
}
}
extension NetBrowser {
func netService(_ sender: NetService,
didAcceptConnectionWith inputStream: InputStream,
outputStream stream: OutputStream) {
print("netServiceDidAcceptConnection:\(sender)");
}
func netServiceWillPublish(_ sender: NetService) {
print("netServiceWillPublish:\(sender)"); //This method is called
}
func netServiceDidPublish(_ sender: NetService) {
print("netServiceDidPublish:\(sender)");
}
func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) {
//debugPrint(errorDict)
print("didNotPublish:\(sender)")
}
}