iOS 12で新規追加されたNetwork Frameworkを使ってUDPによるソケット通信を実装してみました。
以前だとCFSocket
というCore FoundationのクラスでC言語ベースで実装する必要があったところが、Networkフレームワークの登場によりSwiftでSwiftyに書けるようになります。
受信側の実装
NWListener
というクラスを使って、UDPのListenerを実装します。
// 定数
let networkType = "_networkplayground._udp."
let networkDomain = "local"
private func startListener(name: String) {
let udpParams = NWParameters.udp
guard let listener = try! NWListener(parameters: udpParams) else { fatalError() }
listener.service = NWListener.Service(name: name, type: networkType)
let listnerQueue = 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)")
}
private func receive(on connection: NWConnection) {
print("receive on connection: \(connection)")
connection.receive { (data: Data?, contentContext: NWConnection.ContentContext?, aBool: Bool, error: NWError?) in
if let data = data, let message = String(data: data, encoding: .utf8) {
print("Received Message: \(message)")
}
if let error = error {
print(error)
} else {
// エラーがなければこのメソッドを再帰的に呼ぶ
self.receive(on: connection)
}
}
}
送信側の実装
NWConnection
というクラスを利用して、UDPでデータ送信のための準備を行います。(Connectionとは言ってるものの、UDPなのでTCPとは違ってハンドシェイクを行っての接続の確立、みたいなことはしない)
private var connection: NWConnection!
private func startConnection(to name: String) {
let udpParams = NWParameters.udp
// 送信先エンドポイント
let endpoint = NWEndpoint.service(name: name, type: networkType, domain: networkDomain, interface: nil)
connection = NWConnection(to: endpoint, using: udpParams)
connection.stateUpdateHandler = { (state: NWConnection.State) in
guard state != .ready { return }
print("connection is ready")
// do something
...
}
// コネクション開始
let connectionQueue = DispatchQueue(label: "com.shu223.NetworkPlayground.sender")
connection.start(queue: connectionQueue)
}
func send(message: String) {
let data = message.data(using: .utf8)
// 送信完了時の処理
let completion = NWConnection.SendCompletion.contentProcessed { (error: NWError?) in
print("送信完了")
}
// 送信
connection.send(content: data, completion: completion)
}
サービスを探索する
接続相手を見つけるため、Listenerがアドバタイズしているであろうサービス(NWListener.Service
)を探索します。
- 初期化
let netServiceBrowser = NetServiceBrowser()
-
NetServiceBrowserDelegate
プロトコルのデリゲートメソッド群を実装- すべて
optional
- とりいそぎ動作確認したいだけであれば、
netServiceBrowserWillSearch(_:)
(探索スタートする前に呼ばれるのでちゃんと動いてることを確認できる)と、netServiceBrowser(_:didFind:moreComing:)
(サービス発見したときに呼ばれる)を最低限実装しておけばOK
- すべて
extension ViewController: NetServiceBrowserDelegate {
// 探索スタートする前に呼ばれる
func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
}
// サービスを発見したら呼ばれる
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
// 自分以外であれば送信開始
guard service.name != myName else { return }
startConnection(to: service.name)
}
func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber]) {
}
func netServiceBrowser(_ browser: NetServiceBrowser, didFindDomain domainString: String, moreComing: Bool) {
}
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
}
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
}
func netServiceBrowser(_ browser: NetServiceBrowser, didRemoveDomain domainString: String, moreComing: Bool) {
}
}
- 探索開始
netServiceBrowser.delegate = self
netServiceBrowser.searchForServices(ofType: networkType, inDomain: networkDomain)
その他
-
受信・送信両方の機能を1つのアプリに持たせる
- つまりどちらもがListenerになり、どちらもが送信側になりうる
-
アプリ起動時に受信を開始
startListener(name: myName)
- 送信ボタン
@IBAction func sendBtnTapped(_ sender: UIButton) {
send(message: "hoge")
}
実行
- 2台のiOSデバイスを用意する
- 同じネットワークに接続する
- 同アプリを実行
以上で両デバイスで受信準備が完了し、相手を見つけて送信準備も完了(NWConnection.State
が.ready
)したら、送信ボタンを押すたびに相手にメッセージが飛ぶようになります。