NSStreamでクライアントとしてTCPソケット通信を行う その1
今作っているiOSアプリは、以前に作ったAndroid版の移植物なんですが、アカウント認証がwebで、実際のデータは、Androidと共用の自作サービスでやりとりすることになってます。
サーバー側のアプリはTCPで特定のポートをListenし、てきとーに自分で定義したバイナリのプロトコルを使うため、iOSでソケット通信を行う方法をざっくり調査しました。
テクノロジー選択
公式ドキュメントのネットワーキングトピックスに書いてありますが、 iOSでクライアント側からのソケット通信(Stream)をやるなら、
- Objective-CでNSStream (Foundation)
- CでCFStream(CoreFoundation)
のどちらかを使うのが現実的。そして、やりたい事がNSStreamで実現できるなら、NSStreamを使うのが楽だそうです。
確かに、CoreFoundationだとARCじゃなくてややこしいし、Foundationな層とのオブジェクトのやりとりで気を使わないといけないので、NSStreamの方が楽っぽいです(ただし、接続にはCFxxx系を使用するという、全てがFoundationで済まないのがiOSっぽいところなんでしょうか…)
接続
接続先のホスト名/IPアドレスとポートがわかっているなら、
void CFStreamCreatePairWithSocketToHost (
CFAllocatorRef alloc,
CFStringRef host,
UInt32 port,
CFReadStreamRef *readStream,
CFWriteStreamRef *writeStream
);
を使うのが手っ取り早い。hostには IPv4/IPv6の文字列形式のIPアドレスか、FQDNを渡す。
void CFStreamCreatePairWithSocketToCFHost (
CFAllocatorRef alloc,
CFHostRef host,
SInt32 port,
CFReadStreamRef *readStream,
CFWriteStreamRef *writeStream
);
CFHostは正引きの結果をキャッシュするって公式ドキュメントで読んだ覚えがあるので、CFHostオブジェクトを使い回すならこっちの方がいいのかも。頻繁に通信を行わなければならなくなった時に試してみることにします。
結局こちらは不要という結論になりました。詳しくはこちら
そして、CFReadStreamRefとCFWriteStreamRefをNSInputStreamとNSOutputStreamにcastしてやれば接続完了です。
通信イベント
NSOutputStream.delegateのstream:handleEventが呼び出されたらストリームにデータを書き込み、 NSInputStream.delegateのstream:handleEventが呼び出されたらストリームからデータを読み込んでやります。引数でeventの詳細がやってくるので、エラー等は適切に処理してやる必要があります。
サーバー側からの切断
inputStreamのstream:handleEvent:で event内容が NSStreamEventHasBytesAvailableなのに読み出せたデータ長が0になるか、event内容がNSStreamEventEndEncounteredであれば、サーバー側からの切断と判断します。
こちら(クライアント)側からの切断
input/outputのdelegateをnilに設定し、それぞれにcloseを送信。