oFとAIRをOSCのUDP通信で繋げてiPadから操作したりするのにport問題でゲロハマった。

oFはoFxOSC使えば即送信受信できた。簡単。
いつものごとくたどころ先生のところを見て一瞬で理解。本当に感謝。

AIRはTUIOっていうマルチタッチとか検出できるインスタとかに使えそうな素敵ライブラリの中にOSCがあるのでそれ使う。使うのは以下のこれくらいやった。

org.tuio.connectors.udp.OSCDatagramSocket (flash.net.DatagramSocketを拡張したクラス)
org.tuio.connectors.UDPConnector
org.tuio.osc.OSCManager

IOSCListenerってインターフェイスからクラス作って、
そいつをOSCManagerに渡せば(addMsgListener)、
レシーバー関数にOSCで飛んできたやつがぶっ込まれる仕組み。

public function acceptOSCMessage(oscmsg:OSCMessage):void
{
	MonsterDebugger.trace('kita',oscmsg);
}

簡単な参考クラス。

package
{
	import com.demonsters.debugger.MonsterDebugger;

	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.net.DatagramSocket;

	import org.tuio.ITuioListener;
	import org.tuio.TuioClient;
	import org.tuio.connectors.UDPConnector;
	import org.tuio.osc.IOSCListener;
	import org.tuio.osc.OSCBundle;
	import org.tuio.osc.OSCManager;
	import org.tuio.osc.OSCMessage;

	/**
	 * OSC送受信サンプル。
* (注)このサンプルは、UDP通信をつかっているので、Airアプリでしかコンパイル出来ません。 */ public class TestOSC implements IOSCListener { // OSCマネージャー public var _oscManager:OSCManager; private var HOST:String ='192.168.4.100'; // MBP.loacal みたいな名前での参照何故か上手くいかなかったのでローカルIP固定 private var PORT_IN:int=9990; private var PORT_OUT:int=9000; private var stg:Stage; public function TestOSC($s:Stage) { stg = $s; var connectorIn:UDPConnector = new UDPConnector(HOST, PORT_IN); var connectorOut:UDPConnector = new UDPConnector(HOST, PORT_OUT, false); // false でconnect defaultはtrueでbind この辺りはblog記事の下の方の説明参照。 var autoStart:Boolean = true; _oscManager = new OSCManager(connectorIn, connectorOut, autoStart); _oscManager.addMsgListener(this); stg.addEventListener(MouseEvent.CLICK, sendOSCMessage ); } /** * OSC受信 */ public function acceptOSCMessage(oscmsg:OSCMessage):void { MonsterDebugger.trace('kita',oscmsg); if(oscmsg.address == "/hogehoge") { // name hogehogeから飛んできたら処理 var i:int = oscmsg.arguments[0]; } sendOSCMessage(); } /** * * OSC送信 * type * s:A string * i:An integer * f:A float */ public function sendOSCMessage(e:Event=null):void { var m:OSCMessage = new OSCMessage(); m.address = "/mouse/position"; m.addArgument("i", int(mouseX)); // 整数を追加 m.addArgument("i", int(mouseY)); // 整数を追加 m.addArgument("s", "down"); // 文字列を追加。 _oscManager.sendOSCPacket(m); MonsterDebugger.trace('send error.',error); } } }

なにすげー簡単やん。どこハマるの?ってね。

今回iPadのTouchOSC側レイアウト先作って簡単な送信だけの仕組み作っておいた。
oFで受け取るテストやった。送れてるの確認。後はAIRでもこのレシーバー作ったらiPad操作で
AIRとoF動かせる。って感じ。まー経験豊かな方々はすぐに分かるんだろうけど、
この、先に行ったoFとiPadが繋ぎっぱなしの状態で
AIRの送受信アプリ作ってた、これが一番のそして唯一の原因。orz

横で動いてるUDP通信のport番号被ったらAIRのコンパイル時にエラるんですよ。
Error: Can’t send if not connected. って。
いや当然なんだろうけど初めてやってると全然気付かないのね。知らないから。
なんでなんや、、と。

色んな方のblogやらdocsを読み漁っても、当然みんな隣でそんな別UDP通信のアプリ動かしてないわけ。
そりゃ色んな人のサンプルどれでやってもエラるわ。

ということで結果的にできたのはこんな感じでリレー方式で回した。

iPad(TouchOSC) OUT 8100    / IN ****
            ↓
AIR       IN 8100(bind) / OUT 9000(connect)
                      ↓
oF(Receiver)           / IN 9000

で、やっぱり一括で同時操作したいと思ってもうちょっと調べてみる。
なのでここからは僕個人の解決策。

まず初めにUDPConnectorの第三引数であるbindのbooleanでエラーがなくなる。というのが引っかかる。
てことで DatagramSocket のbindとconnectの違いを調べる。
(検索で datagramsocket bind vs connect とか出てくるw)

asdocには
http://help.adobe.com/ja_JP/air/reference/html/flash/net/DatagramSocket.html

bind(localPort:int = 0, localAddress:String = “0.0.0.0”):void
指定されたローカルのアドレスおよびポートにこのソケットをバインドします。

connect(remoteAddress:String, remotePort:int):void
指定されたリモートアドレスおよびポートにソケットを接続します。と。

んー。

bindだとエラるけどconnectだと大丈夫みたいなことがちらほら書かれてたし
実際に経験したので感覚値でしかないけれどbindだと他と被るとエラるし、
connectだと大丈夫とかそんな感じなのかなぁとか。

そこで

iPad(TouchOSC) OUT 8100      / IN 9000
            ↓
AIR        IN 8100(connect) / OUT 9000(connect)
            ↓
oF(Receiver)   IN 8100     /

として同時に受け取るようにしてみる。ちなみにconnectにすればやっぱりエラらない。
てわけでbindは1つしか存在できないしconnectは被っても大丈夫みたいな解釈に落ち着いた。

んでこの結果はどちらか先に立ち上げたアプリしか動かない。

やっぱりbindで繋げるとそのportを占有するって感じなのね。把握。

そしてこの3分後にひこさんに教えてもらう。1日かけて理解したことを5分で理解できる。
ほんとありがとうございました。orz w

bindは自分がどこのportで待ち受けてるよーってもんで
connectは自分からどこかにつなぎにいくよーってもん。

なるほどな!!!!!!!!!!!

だからbindは1つのport使われてたら後からきたら使えないし、(AIRのコンパイル時にはエラる)
connectは複数来てもいける。というか送信用。はあく。:)
てことはなんかどっかのblogに上がってた記事、UDPConnectorのINとOUTのbind引数間違ってる気がする。
両方一緒だったし。まーその人の用途が自分と違うだけなので全てを鵜呑みにしてやんなっつー話しですね。

結果的に試行錯誤で正しい使い方には行けたけど、
今回はやっぱりちゃんと意味を教えてもらって理解できたのですげーすっきり。:)

そしてリレー方式ではなくOSCulatorの存在も教えてもらった。ひゃっほう\(^o^)/
(ちょっと試したけどMIDI変換とかまでできんのね。課金しないとアラート出てぶった切られるから今回はちょっと保留。)
(でも確かにこれは便利!!)

最後にbind()とconnect()のas docsも転載しておしまい。:)

you

———————-

bind() メソッド
public function bind(localPort:int = 0, localAddress:String = “0.0.0.0”):void

ランタイムバージョン: 2

指定されたローカルのアドレスおよびポートにこのソケットをバインドします。

bind() メソッドは、同期的に実行されます。バインド操作は、コードの次の行が実行される前に完了します。

パラメーター
localPort:int (default = 0) — ローカルコンピューター上でバインドするポートの数。localPort が 0(デフォルト)に設定されている場合、次に利用可能なシステムポートがバインドされます。1024 より下位のポート番号に接続する権限には、システムのセキュリティポリシーが適用されます。例えば、Mac および Linux システムでは、アプリケーションは 1024 より下位のポートに接続するにはルート権限で実行する必要があります。

localAddress:String (default = “0.0.0.0”) — バインド先のローカルマシンの IP アドレス。このアドレスは、IPv4 アドレスでも IPv6 アドレスでもかまいません。localAddress が 0.0.0.0(デフォルト値)に設定されている場合、ソケットは利用可能なすべての IPv4 アドレスを監視します。利用可能なすべての IPv6 アドレスを監視するには、”::” を localAddress 引数として指定する必要があります。IPv6 アドレスを使用するには、コンピューターとネットワークの両方が IPv6 をサポートするように設定されている必要があります。 さらに、IPv4 アドレスにバインドされたソケットは IPv6 アドレスのソケットに接続できません。同様に、IPv6 アドレスにバインドされたソケットは IPv4 アドレスのソケットに接続できません。アドレスの種類が一致している必要があります。

例外
RangeError — このエラーは、localPort が 0 未満か、または 65535 より大きい場合に発生します。
ArgumentError — このエラーは、localAddress が構文的に整形式の IP アドレスになっていない場合に発生します。
IOError — このエラーは、ソケットをバインドできない状況で発生します。例えば、次のような場合です。

localPort が既に別のソケットで使用されている場合。
アプリケーションを実行するユーザーアカウントに、指定されたポートへバインドする十分なシステム権限がない場合(権限の問題は、localPort < 1024 の場合に発生する場合があります)。 この DatagramSocket オブジェクトが既にバインドされている場合。 この DatagramSocket オブジェクトが既に閉じている場合。 Error — このエラーは、localAddress が有効なアドレスでない場合に発生します。 例 ( この例の使用方法 ) 次の例では、様々な方法で DatagramSocket オブジェクトをバインドする方法を示します。 udpSocket.bind(); //bind to any available port, listen on all IPv4 addresses udpSocket.bind( 0, "0.0.0.0" ); //same as above udpSocket.bind( 0, "127.0.0.1" ); //any available port on the localhost address udpSocket.bind( 8989, "192.168.0.1" ); //port 8989 on a particular IPv4 address udpSocket.bind( 0, "::" ); //any available port on all IPv6 address udpSocket.bind( 8989, "::1" ); //port 8989 on the IPv6 localhost address udpSocket.bind( 8989, "2001:1890:110b:1e19:f06b:72db:7026:3d7a" ); //port 8989 on a particular IPv6 address ---------------------- connect() メソッド public function connect(remoteAddress:String, remotePort:int):void ランタイムバージョン: 2 指定されたリモートアドレスおよびポートにソケットを接続します。 データグラムソケットが「接続されている」場合、データグラムパケットは指定されたターゲットからのみ送受信できます。 その他のソースからのパケットは無視されます。データグラムソケットへの接続は必須ではありません。接続を確立すると、他のソースから不要なパケットを除外する必要がなくなります。ただし、UDP ソケット接続は(TCP 接続用なので)永続的なネットワーク接続ではありません。 ソケットのリモートエンドが存在していない可能性があります。 bind() メソッドが呼び出されなかった場合、ソケットは自動的にデフォルトのローカルのアドレスとポートにバインドされます。 パラメーター remoteAddress:String — 接続を確立するリモートマシンの IP アドレス。 このアドレスは、IPv4 アドレスでも IPv6 アドレスでもかまいません。bind() が呼び出されなかった場合は、デフォルトの bind() を呼び出すときに remoteAddress のアドレスファミリー(IPv4 または IPv6)が使用されます。 remotePort:int — 接続の確立に使用するリモートマシン上のポート番号です。 例外 RangeError — このエラーは、localPort が 1 未満か、または 65535 より大きい場合に発生します。 ArgumentError — このエラーは、localAddress が構文的に有効なアドレスになっていない場合に発生します。または、デフォルトのルートアドレス('0.0.0.0' または '::')が使用されている場合に発生します。 IOError — このエラーは、ソケットを接続できない場合に発生します。例えば、connect() の呼び出しの前に bind() が呼び出されていなかった場合や、リモートアドレスファミリーへのバインドができない場合などです。