発端は、.NETリモート通信を metro アプリから desktop アプリ(通常のwin8アプリ)に対して通信をさせたかった、ということです。metro アプリのデバッグログなんかを win8 アプリから見れたり、リモートデバッグしている元の PC から見られるようにするのが目的だったのです。
# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。
が、実は metro アプリでは .NET リモート通信ができません。.NET リモート通信を行うための条件として、
- TCP/IP 通信ができること(内部ではHTTPで動作している)。
- 共通のクラスを MarshalByRefObject で継承できること。
- 共通のクラスを、「共通」で使えること。
になるわけですが、metro って上記の3つとも駄目で、
- 直接 TCP/IP するクラスが用意されていない。ただし、HTTP だけは HttClient クラスがある。
- MarshalByRefObject クラスがない。
- metro のフレームワークと、.NET Framework 4.5 でそもそも異なる。
ってな感じで共通にクラスが使えません。まぁ、Windows Phone やら、他のバージョンのフレームワークに対しては「同じクラス」をアセンブリレベルでは共通に使えないので、当たり前といえば当たり前なのですが、同じ PC 内で、metro – desktop 間でアクセスできないのは、ちょっと辛い。
Client/Server の関係としては、metro が必ず Client になります。というのも、metro はばっくぐらうんどに入ってしまうとサスペンドをしてしまうので、Server にするにはあまり意味がないんですよね。それでも、iPhone の画像転送用の簡易 Http サーバーみたいなことができると便利なのですが、metro app の場合は desktop アプリとワンセットになって windows 8 として組み込まれるので、metro app 単体でなくても役に立つのです。まぁ、インストール自体が発生してしまうので、metro app の簡便性というのは低くなるのと、Store のほうがどうなのかという問題は残りますが…業務アプリならば別に問題なし。
■desktop のサーバーアプリ
HttpListener クラスを使って簡易的な HTTP サーバーを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | using System.Net; using System.IO; namespace SampleProcessCommServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); } // リスナー HttpListener listener; private void button1_Click( object sender, EventArgs e) { // 受け付けるURL string url = "http://*:8081/metro/" ; // 開始 this .listener = new HttpListener(); this .listener.Prefixes.Add(url); listener.Start(); // スレッド開始 backgroundWorker1.RunWorkerAsync(); listBox1.Items.Add( "サーバー開始" ); } private void button2_Click( object sender, EventArgs e) { // 停止 } private void backgroundWorker1_DoWork( object sender, DoWorkEventArgs e) { while ( true ) { var content = listener.GetContext(); var request = content.Request; var response = content.Response; var output = new StreamWriter(response.OutputStream); string text = @"<response> <name>SampleProccessCommServer</name> <date>" + DateTime.Now.ToString() + @"</date> </response> " ; output.WriteLine(text); output.Close(); } } } } |
画面をブロックしないように、BackgroundWorker コンポーネントを使っていますが、.NET 4.5 なので例の async/await を使っても ok です。
URL の指定の仕方は、HttpListener クラス (System.Net) を参考にしてください。URL の書き方は、.NET リモートと似ています…というか、内部的にこれを使っているかと思われます。
レスポンスは XML 形式で書いていますが、文字列であればなんでも ok です。クライアント側の解析のためは XML 形式にしておいたほうがよいでしょう。
■desktop のクライアントアプリ
テスト用に desktop 版のクライアントアプリを作って試します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using System.Net; using System.IO; namespace SampleProcessCommClient { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click( object sender, EventArgs e) { // 送信 var client = new WebClient(); string url = "http://localhost:8081/metro/sample001" ; var output = new StreamReader( client.OpenRead(url)); string content = output.ReadToEnd(); output.Close(); textBox2.Text = content; } } } |
接続先は自分自身なので「localhost」にしておきます。.NET リモート風にアドレスを指定しておいて、最終的にはサーバーのほうで分岐させるという感じになります。
さて、これでテストをするときに、サーバープログラムのほうで、「System.Net.HttpListenerException: アクセスが拒否されました。」とエラーがでます。実は、windows 7 あたりから、一般ユーザーの場合はサーバーのポートを開く権限がないのですよね。「System.Net.HttpListenerException: アクセスが拒否されました。」と表示されてしまう を参考にしてポートを設定するか、アプリを「管理者権限」で立ち上げてしまいます。
ポートの設定は、「netsh http add urlacl url=http://*:8081/metro/ user=masuda」のようにします。
すると無事に送受信ができます。
■metro アプリから接続する。
先にテストしたクライアントアプリを metro に書き写してしまえば ok …と思いきや、かなりコードが違います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | using System.Net.Http; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238 namespace SampleProcessComm { /// <summary> /// An empty page that can be used on its own or navigated to within a Frame. /// </summary> public sealed partial class BlankPage : Page { public BlankPage() { this .InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } private async void Button_Click_1( object sender, RoutedEventArgs e) { var client = new HttpClient(); // GET で呼び出し var res = await client.GetAsync( "http://localhost:8081/metro/sample1" ); // レスポンスを取得 var cont = await res.Content.ReadAsStringAsync(); textBoxOutput.Text = cont; } } } |
connect も get も非同期になるので、async/await を使う、ってのと HttpClient クラスを使うってのがミソです。
実行した結果がこんな感じ。input のところは無視していますが、きちんとレスポンスが返ってきます。
このソースの場合は localhost に送ったので、同じ PC に対してプロセス間通信をしていますが、別の PC に飛ばすこともできるので、実質 .NET リモートと同じように動作ができます。で、適当なシリアライズを考えてやれば、なにも MarshalByRefObject を継承する必要はなくて、異なる .NET Framework のバージョン間でも大丈夫という具合。当然、XML-RPC でも動くわけですから、相手が PHP や Java でも大丈夫。
まあ、このまま使うのはあまりにも面倒なので、適当にラップする必要がありますが…ってのと、Store とかに載せた場合はどうなるの?ってのがありますが。一応、プロセス間通信はできるということで。
リ
http://soft62.cf/11-minutes-by-paulo-coelho-ebook-free.html 11 minutes by paulo coelho ebook free
ローカルループバックができない問題はこれで解決できませんでしょうか?
ループバックを有効にする方法とネットワーク分離のトラブルシューティングを行う方法 (Windows ランタイム アプリ)
https://msdn.microsoft.com/ja-jp/library/windows/apps/hh780593.aspx