Quantcast
Channel: その後のその後
Viewing all articles
Browse latest Browse all 314

[iOS 12]Network FrameworkでUDPソケット通信

$
0
0

iOS 12で新規追加されたNetwork Frameworkを使って、UDPによるソケット通信を実装してみました。

以前だとCFSocketというCore FoundationのクラスでC言語ベースで実装する必要があったところが、Networkフレームワークの登場によりSwiftでSwiftyに書けるようになります。

受信側の実装

NWListenerというクラスを使って、UDPのListenerを実装します。

// 定数letnetworkType="_networkplayground._udp."letnetworkDomain="local"
privatefuncstartListener(name:String) {
    letudpParams= NWParameters.udp
    guardletlistener= try! NWListener(parameters:udpParams) else { fatalError() }
    
    listener.service = NWListener.Service(name:name, type:networkType)

    letlistnerQueue= DispatchQueue(label:"com.shu223.NetworkPlayground.listener")
    
    // 新しいコネクション受診時の処理
    listener.newConnectionHandler = { [unowned self] (connection:NWConnection) in
        connection.start(queue:listnerQueue)
        self.receive(on:connection)
    }
    
    // Listener開始
    listener.start(queue:listnerQueue)
    print("Start Listening as \(listener.service!.name)")
}

privatefuncreceive(on connection:NWConnection) {
    print("receive on connection: \(connection)")
    connection.receive { (data:Data?, contentContext:NWConnection.ContentContext?, aBool:Bool, error:NWError?) inifletdata= data, letmessage= String(data:data, encoding: .utf8) {
            print("Received Message: \(message)")
        }

        ifleterror= error {
            print(error)
        } else {
            // エラーがなければこのメソッドを再帰的に呼ぶself.receive(on:connection)
        }
    }   
}

送信側の実装

NWConnectionというクラスを利用して、UDPでデータ送信のための準備を行います。(Connectionとは言ってるものの、UDPなのでTCPとは違ってハンドシェイクを行っての接続の確立、みたいなことはしない)

privatevarconnection:NWConnection!privatefuncstartConnection(to name:String) {
    letudpParams= NWParameters.udp
    // 送信先エンドポイントletendpoint= NWEndpoint.service(name:name, type:networkType, domain:networkDomain, interface:nil)
    connection = NWConnection(to:endpoint, using:udpParams)
    
    connection.stateUpdateHandler = { (state:NWConnection.State) inguard state != .ready else { return }
        print("connection is ready")

        // do something...
    }
    
    // コネクション開始letconnectionQueue= DispatchQueue(label:"com.shu223.NetworkPlayground.sender")
    connection.start(queue:connectionQueue)
}

funcsend(message:String) {
    letdata= message.data(using: .utf8)
    
    // 送信完了時の処理letcompletion= NWConnection.SendCompletion.contentProcessed { (error:NWError?) in
        print("送信完了")
    }

    // 送信
    connection.send(content:data, completion:completion)
}

サービスを探索する

接続相手を見つけるため、Listenerがアドバタイズしているであろうサービス(NWListener.Service)を探索します。

初期化
letnetServiceBrowser= NetServiceBrowser()
NetServiceBrowserDelegateを実装
  • すべてoptional
  • とりいそぎ動作確認したいだけであれば、netServiceBrowserWillSearch(_:)(探索スタートする前に呼ばれるのでちゃんと動いてることを確認できる)と、netServiceBrowser(_:didFind:moreComing:)(サービス発見したときに呼ばれる)を最低限実装しておけばOK
extensionViewController:NetServiceBrowserDelegate {
    // 探索スタートする前に呼ばれるfuncnetServiceBrowserWillSearch(_ browser:NetServiceBrowser) {
    }

    // サービスを発見したら呼ばれるfuncnetServiceBrowser(_ browser:NetServiceBrowser, didFind service:NetService, moreComing:Bool) {
        // 自分以外であれば送信開始guard service.name != myName else { return }
        startConnection(to:service.name)
    }
    
    funcnetServiceBrowser(_ browser:NetServiceBrowser, didNotSearch errorDict:[String : NSNumber]) {
    }
    
    funcnetServiceBrowser(_ browser:NetServiceBrowser, didFindDomain domainString:String, moreComing:Bool) {
    }
    
    funcnetServiceBrowserDidStopSearch(_ browser:NetServiceBrowser) {
    }
    
    funcnetServiceBrowser(_ browser:NetServiceBrowser, didRemove service:NetService, moreComing:Bool) {
    }
    
    funcnetServiceBrowser(_ browser:NetServiceBrowser, didRemoveDomain domainString:String, moreComing:Bool) {
    }
}
探索開始
netServiceBrowser.delegate =self
netServiceBrowser.searchForServices(ofType:networkType, inDomain:networkDomain)

その他

  • 受信・送信両方の機能を1つのアプリに持たせる

    • つまりどちらもがListenerになり、どちらもが送信側になりうる
  • アプリ起動時に受信を開始

startListener(name:myName)
  • 送信ボタン
@IBActionfuncsendBtnTapped(_ sender:UIButton) {
    send(message:"hoge")
}

実行

  • 2台のiOSバイスを用意する
  • 同じネットワークに接続する
  • 同アプリを実行

以上で両デバイスで受信準備が完了し、相手を見つけて送信準備も完了(NWConnection.State.ready)したら、送信ボタンを押すたびに相手にメッセージが飛ぶようになります。

Special Thanks

本実装はSwift Islandというカンファレンスでの Roy Marmelstein氏のワークショップで学んだ実装を自分なりに咀嚼して(復習・覚書として)書き直したものです。

WWDC18の当該セッションもまだ見てなくて、たぶんワークショップに参加しなかったら自分では当面さわらなかったんじゃないかと思うフレームワークなのですが、こうして自分でやってみると(CF時代の不慣れなC言語による難しさ成分が取り除かれているため)意外と扱いやすいことがわかってよかったです。もうちょっとネットワークプロトコルの気持ちがわかりたいなと思ってた頃なので、引き続きさわっていきたい所存です。


Viewing all articles
Browse latest Browse all 314

Trending Articles