iOSでコードを組む場合、クライアントなんかは充実しているように感じます。例えばRESTFulをするHTTPクライアントはサービスペペっと呼べばまぁ良い感じに出来ます。が、サーバはそうもいかない印象があります。まぁ普段使わないし、いろいろ面倒事もありますので・・
更にSwift3になって、サーバ系のライブラリが色々使えなかったりとかしたり(ただ最近は是正されつつあります)。
例えばお外でBLEなデバイスのデータを受信解析とかして、その結果を別なノーパソから取る場合、サーバにした方がむしろ楽な時があります。サーバにしといて、状況をcurlで引っ張ってRで可視化とか、簡単な統計をHTMLにしてブラウザで見たりとかです。個人的には回線とBLEも使える実験用データサーバとしてスマホ(私の場合はiPhone)は優れものだと思います。
そういう事を行うためのHTTPサーバっぽいプログラムを作ってみました。
で、何ができるの?
なんちゃってHTTPサーバもどきとして、以下の事しかできません。
- プログラムで生成されたHTMLを表示
- バンドルされたファイルを転送
- プログラムで生成したDocumentファイルを転送
- IPv4でのみ動作します。
上記を実現するため、以下の機能をもっています。
- ファイルの読み書き
- バンドルされたファイルのパス取得
- プログラムで書いてDocumentに入れたファイルのパスを取得
- ファイルアクセス関連で使う UInt8配列とかNSData関係のオブジェクト変換
- 同じくStringと上記オブジェクトとの変換
- 通信と連動してView系を更新する
- socketを使った通信(TCPサーバ)
iOSに慣れた方にとっては、socketを除き上記はしょーもない機能なのかもしれません。が、普段やってない人にとってはSwift3の変更もあって、ハードル高いです。そういう機能もAPIにしてみましたので、慣れない方の参考になると・・・いいな。
TCPサーバを実現するコード
具体的にどう実現したかは、GitHubにxcodeのプロジェクトを入れましたので、そのコードを見てもらった方が早いです。gdSampleServerフォルダにあるswift達の内訳を書きます。
URL
https://github.com/gdaigo82/iosSampleServer
ファイル名 | 内容 |
---|---|
ViewController.swift | main的な処理。UIの配置など |
TinyHTTPServer.swift | 以下を利用したHTTPサーバ処理 |
SimpleTCPServer.swift | socketを使ったTCPサーバ |
SimpleFileService.swift | ファイルアクセス関係 |
SimpleCharacter.swift | 画像とかテキストの表示を担当 |
ToolSample.swift | UInt8、NSData、のオブジェクト交換、時刻・IP取得など雑多なツール |
私同様、慣れていない方が何らかの参考にしていただくとか、利用していただければ幸いです。
ちなみにTinyHTTPServer.swiftを見てもらうと分かりますが、HTTPとしてはかなりナメた実装なので、あくまでも参考として下さい(苦笑)
評価方法
動かして戴ける方のために、簡単に動かし方を説明します。まずはgitから落とすかzipを解凍します。
URL
https://github.com/gdaigo82/iosSampleServer
次に適当なJPEG画像をsample.jpgとして用意し、それをgdSampleServer/gdSampleServerフォルダに入れます。
その後、xcodeでビルドし、実機もしくはiOSシミュレータで起動してください。例によってですがプロ生ちゃんが出てきます。
#ちなみに実機ですと、本当は3G回線のIPも出ます(つまり2行表示されます)。上の図では一応消させていただきました。
このように生IPでURLが表示されます。またこのアプリは縦固定としています。で、実際にはLAN内のIPを使う事になるかと思います(図だと10.0.0.4)。LAN内のブラウザからまずはそのまま(図なら 10.0.0.4:8081 )打ち込むと、ブラウザに時刻が表示され、iOS上の画面も変化します。
この他にも、画像を出したり、あるいはログ(転送サイズ)を出したりできます。URLはそれぞれ /img と /log です。例えばIPが10.0.0.4ならば以下のURLに対応しています。ポート番号はハードコーディングしてますので、必要ならSwiftいじって修正していただければ。
- 10.0.0.4:8081/img で画像
- 10.0.0.4:8081/log でログ(総転送サイズ)
- 10.0.0.4:8081 でHTML(時刻)-というか上2つ以外すべてこれ。
また、プロ生ちゃんをクリックしますと初期画面に戻りますので、IPの再確認とかできます。
という事で、一応Webブラウザでデータが拾えているのが分かるかと思います。
TIPS
コードを組みにあたり、ひっかかったトコなどをまとめておきます。
IP取得
いつものstack overflowさんです。以下を参考というかほぼ丸々使わせて戴きました(IPv6は使わないようにしたぐらいですね)
socket系をswiftで実現するためのxcode8の操作
特殊なヘッダをプロジェクトに追加する必要があります。以下が参考になりました。但しパスは少し工夫してます(プロジェクトフォルダをこれは直接指定しました)
bind系
手前味噌で恐縮ですが・・
時刻取得
手前味噌で恐縮ですが2
マルチスレッド処理とUIとの連携
一般にGUIを持つシステムでマルチスレッドやると、Viewを管轄するスレッド以外からのUIアクセスについて、気にかける必要があります。iOSもそうです。以下、TinyHTTPServer.swiftから該当処理を抜粋してみました。とりあえずこれで動くようです。
// UIと別スレッドをこんな感じで処理させてみました。
//
func mainFunc(label : String) {
let queue = DispatchQueue(label: label)
queue.async { // ここでViewControlerのスレッドから切り離します。
self.serverMainSequence() // HTTPサーバのメイン処理(connectからrecv,sendしてshutdownまで)
DispatchQueue.main.async { // ここでViewControlerのスレッドに戻ることを期待
// 戻ったので、viewに対していろいろやらかします。
var text = (self.tool?.getNowClockString())!
text += " " + self.count.description + "回目\n"
text += self.serif!
self.character?.reaction(serif: text, image: self.image!) // これが表示処理です。
self.count += 1
self.mainFunc(label: "gdaigo82.server") // 最後に自分を呼び出します。
}
}
}
これ、本来は以下のように処理したいのです。
while (true)
{
self.serverMainSequence() // HTTPサーバのメイン処理(connectからrecv,sendしてshutdownまで)
self.character?.reaction(serif: text, image: self.image!) // これが表示処理です。
}
ただ、実際HTTP系は別スレッドで処理させたかったので、DispatchQueueを使ってみました。どうもqueue.async {}とすると、{}の中は別スレッドになるみたいです。更にその中でDispatchQueue.main.async {} と書くと、その上の処理が終わった際にmainつまりUIを担当するスレッドに切り替えてくれ、結果 {}の中ではUIが操作可能になるっぽいです。
なので、通信系とUI表示を連動させたければ、このような書き方をすれば出来るみたいです。
本件、以下が大変参考になりました。
ライセンス
以下使わせて戴きました。素晴らしいソフトウェアを提供して下さり、ありがとうございます。
- 本プロダクトはMITライセンスとします。詳細はGitHubを。
- プロ生ちゃんの画像は「プロ生ちゃん利用ガイドライン」に従った利用が必要ですので、ご注意ください。
以上です。