以前、名古屋のFxugにて少し発表した内容なんですが、ずっと記事にできてなかったので、今更ながらまとめます。
密かに前から社内用発表会のネタとして作ってはいたのですが、僕個人的にも、なかなか披露する機会がないまま、時間が過ぎてしまいました。
概要としては、
1. iPhone上でボタンをタップすると、AIR 2で作ったサーバーに、iPhoneからSocket経由で信号を送ります。
2. AIR 2のサーバーが、信号を受け取り、信号に応じた処理を行います。
これが作ったアプリのキャプチャです。
iPhoneとAIR 2サーバーの関係は、この通りです。
簡単に行ってしまえば、これだけです。
では、ここから説明します。
1. AIR 2のサーバー部分を作成
AIRでサーバーを作るには、UDPとTCPのサーバーがありますが、今回はTCPで通信するので、flash.net.ServerSocketクラスを使用します。
宣言〜受付開始
<pre>import flash.net.ServerSocket; // 接続してきたSocketを保持する var socks:Vector. = new Vector.(); // インスタンス生成 var server:ServerSocket = new ServerSocket(); // 接続時のイベントハンドラ作成 server.addEventListener(ServerSocketConnectEvent.CONNECT, serverSocketConnectHandler, false, 0, false); // 待ち受けポートを指定(43243番で受け付ける) server.bind(43243); // 待ち受け開始 server.listenen();</pre>
接続時:
function serverSocketConnectHandler(event:ServerSocketConnectEvent):void { // 接続してきたSocket var socket:Socket = event.socket; // 接続してきたSocketにデータを返すため、イベント設定 socket.addEventListener(ProgressEvent.SOCKET_DATA, receiveHandler, false, 0, false); // 接続してきたSocketを保持する socks.push(socket); }
受信時:(本来は宛先を指定するべきだが、特に指定せず、全てのSocketに送信しています)
function receiveHandler(event:ProgressEvent):void { // データを送信したSocketを取得 var socket:Socket = event.target as Socket; if(socket!=null) // nullチェック { // 受信データを取得 var recv:String = socket.readUTFBytes(socket.bytesAvailable); // 操作対象のVideoPlayerを取得 var target:VideoPlayer = getCurrentVideo(); // 自作のメソッドです // コマンドを取得し、操作を実行。コマンドを取得するため、indexOfを使っています。 if( recv.indexOf("prev") >= 0) { setPos("prev"); } else if( recv.indexOf("next") >= 0) { setPos("next"); } else if( recv.indexOf("play") >= 0) { if( target.playing ) { target.pause(); } else { target.play(); } } } }
AIRで作るサーバー周りは、こんなところです。エラー処理や切断時の処理などは書いてなくてごめんなさい。
iPhone側アプリケーションのSocket処理
では、次。iPhoneのほう。これも簡単な書き方でごめんなさいね。と、いうかCで最低限の実装しかしていないです。
TCPSendClient.m
接続:
-(void)connect:(NSString *)ip toPort:(NSInteger)port { if(_connected) // TCPSendClientが持っている、接続状態を保持する変数 { return; } _connected = NO; // IPアドレスを取得 servIP = [ip UTF8String]; // portを取得 servPort = port; // socketが正しく生成できているかチェック if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { NSLog(@"socket creation error."); return; } // socketの設定 memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(servIP); servAddr.sin_port = htons(servPort); // 接続 if(connect(sock, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) { NSLog(@"connect error."); return; } _connected = YES; NSLog(@"connect succeed."); }
送信:
-(void)send:(NSString *)message { mes = [message UTF8String]; mesLen = strlen(mes); // 送信 if(send(sock, mes, mesLen, 0) != mesLen) { NSLog(@"send failed."); } }
ちなみに、
TCPSendClientクラスは、Socket処理を受け持つクラスで、自作しています。
TCPSendClient.hはこんな風に書いています。
#import <Foundation/Foundation.h> #import <stdio.h> #import <sys/socket.h> #import <arpa/inet.h> #import <stdlib.h> #import <string.h> #import <unistd.h> @interface TCPSendClient : NSObject { int sock; struct sockaddr_in servAddr; unsigned short servPort; const char *servIP; const char *mes; unsigned int mesLen; BOOL _connected; } -(void)connect:(NSString *)ip toPort:(NSInteger)port; -(void)send:(NSString *)message; -(void)close; -(BOOL)getConnected; @property(readonly) BOOL connected; @end
このサンプルでは、このようにTCPSendClientを使っています。
PagerViewController.m
-(IBAction) processConnect:(id)sender // ボタンをタップした時のAction { if([connectSwitch isOn]) // これは、接続のon/offを切り替えるUISwitchです。 { [sendc connect:@"10.0.2.1" toPort:43243]; // sendcというのがTCPSendClientです stateField.text = @"connect"; } else { [sendc close]; // 本文では紹介しなかったですが、切断します。 stateField.text = @"disconnect"; } }
データ送信:
-(IBAction) btnUp:(id)sender // 各ボタンのTouchUpInsideと関連づけています。 { isPress = NO; prevValue = 0; if( (UIButton*)sender == btn) // btnとは、一つ前の動画にフォーカスをあてるためのボタンです。 { dir = @"prev"; [sendc send:dir]; } else if((UIButton*)sender == nextBtn) { // nextBtnとは、一つ次の動画にフォーカスをあてるためのボタンです。 dir = @"next"; [sendc send:dir]; } else if((UIButton*)sender == playBtn) { // playBtnとは、フォーカスがあたっている動画を再生するためのボタンです。 dir = @"play"; [sendc send:dir]; } }
ちなみに、こちらがiPhoneアプリの画面です。(すごい地味ですね。。。笑えない)
Interface Builderのキャプチャです。
Macをルーターにする
最後に、iPhoneとAIRサーバーを接続するための設定をします。Macなら「インターネット共有」を使えば簡単にルーターにして連携させる事が可能です。こういうときに、一つのメーカーが作っている事が良い事に思えますね。
今回は、AirMac経由で共有します。
1. 「システム環境設定」を開く
2. 「共有」を開く
3. 「インターネット共有」を選択する
4. 「共有する接続回路」がAirMac以外であることを確認。なっていなければ変更します。
→iPhoneとMacをAirMac(無線)で接続する
5. 「インターネット共有」のチェックを入れると、共有を開始するか確認してきますので、開始をクリックします。すると、画面右のAirmacのアイコンがこのようになります。
開始順としては、AIRサーバーとiPhoneが接続する必要があるので、
1. Macをルーターにする
2. AIRサーバーアプリを起動する
3. iPhoneアプリを起動して接続する。
4. iPhone上で操作する。
というところです。
今回簡単に紹介しすぎててすみません。このままでわかりにくいなどご意見ありましたら整理した上で、ソース込みで全て公開します。
はじめまして。
いつも参考にさせていただいています。
いきなりで大変恐縮なのですが、こちらのソース群を公開していただけないでしょうか?
iPhoneからLinux系サーバへTCPで接続するアプリを作成しようと考えていまして、
サーバ側はどうにかできるのですがObjective-Cの方がどうも・・・
ですので、公開していただけると助かります。
よろしくお願いします。
netさん、コメントありがとうございます。
iPhone側のソースを下記にアップしましたのでご確認下さい。
「ダウンロード」からいっていただけるとgitを使わなくてもダウンロードできます。
http://github.com/mmlemon/Pager/tree/master/Pager/
公開していただきありがとうございました。
これでなんとかなりそうです。
またお世話になると思いますので
その時はよろしくお願いします。
netさん
お役に立てたなら何よりです。
基本的に通信周りはTCPSendClientクラスで行っているのですが、Close処理等まだまだ甘い所がありますのでもし本格的なアプリを作られる場合はご注意下さいね。