昨日の.NETラボの飲み会で話した「通信プロトコルに層がひとつ足りない」の話をちょっとメモ書き。
Siverlight のサーバーアクセスは WCF だったり、ASP.NET MVC からのデータベースアクセスは Entity Framework だったりする訳ですが、内部的には、TCP/IP を生のプロトコルで通しています。正確には HTTP on TCP/IP ってことで、HTTP プロトコルな訳ですが、alive http であろうと、session 毎に切ってしまう http だろうと、ちとある意味で、アプリケーション層と プロトコル層(TCP/IP層) – 用語は、正確ではないのですが — が直結しているために、データのやり取り(特に Connection や 例外関係)に無理があります。
結論から言うとですね、H さんとか、I さんとか、F さんとか、で業務アプリで通信データのやり取りをすると、必ず、TCP/IP を 2 セッションはります。
TCP/IP は、サーバー/クライアント方式ですから、
Clinet -> Server
の接続が必須なんですね。これを等価にするためには、
Client -> Server
Server <- Clinet
の2セッションが必須になるわけです。何故、2セッション必要なのか、双方向通信にする必要があるのかというと、Server 側はから、なんらかの通知をしたいとき(例外発生、異常発生時)に、Client -> Server の一方向だけだと、Client がポーリングをしていない限り、通知を受け取れないのです。つまり、Client が Get しない限り、Server から緊急通知を受けれないというタイムラグが発生します。
なので、サーバーからクライアントへの通知のために、もう1本コネクションを使います。
で、この2つのセッションですが、それぞれの会社で独自の実装をしています。と言いますか、プロジェクト単位で独自の実装をしています。まぁ、開発標準的なものもあるんですが、結局、ライブラリを使って独自の使い方になっちゃう。
これをデータベースアクセスにして考えてみると、Siverlight の非同期通信と、DB アクセスの同期通信が微妙に交差してしまうのです。
- Siverlight で、WPF 経由で Web サーバーをコール
- 結果待ちになる
- Web サーバーが、DB へ同期通信
- Web サーバーが、Silverlight へ WPF 経由で結果を返す。
- Silverlight が、イベントを発生させる。
という流れになっています。
Silverlight は、一見、非同期通信をしているように見えますが、データベースアクセス的に同期通信に縛られるんですね。これは、.NET Framework プログラミングの第3版を読んでいて気づいたのですが、Silverlight の通信中に他のことができるようにする、ということと(UX的にという意味で)、DB アクセスをしてデータを拾ってくるという意味とは微妙に異なります。
なので、理想的な非同期通信を書き直すと、
- Silverlight で、WPF 経由で Web サーバーをコール
- Web サーバーは WPF をキャッシュする
- Web サーバーは DB へ同期通信
- Web サーバーは WPF キャッシュを利用して、Siverlight 自身へ接続
- そしてデータ通信
- Siverlight は、イベントを発生させる。
という流れになります。一見、先の流れと同じように見えますが、4 のところで、改めて接続しているところが違います。
1 本目のセッションでは、Silverlight から Web サーバーへの WPF コール
2 本目のセッションでは、Web サーバーから Siverlight へのデータコール
になります。
こうすると、Siverlight のほうは、データ受信待ちのポーリングが必要なくなるので、動作が軽くなります。
で、本来の「セッション」の意味づけとして、等価な双方向通信として、この 2 本のセッションをまとめて「セッション」と呼ぶような層が必要、ということなのです。
擬似コードで言うとこんな感じですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private DualConnection _cn = new DualConnection void Setup() { // イベントをセットアップ _cn.OnConnectin += OnConnection; _cn.OnRecv += OnRecv; _cn.OnClose += OnClose; } void Connect() { _cn = new DualConnection() // ホスト名を指定するが、どちらから接続してもOK _cn.Open( hostname ); } void OnConnect() { // 相手から接続してきた場合 } void OnRecv( byte [] data ) { // 受信データ } void OnClose() { // 切断処理 } |
という形で定義しておいて、
- Listen は、いらないので、いきなり OnConnection 後に OnRecv が入ってくる。
- 送信は普通に _cn.Send のように送る。
という層をひとつ用意します。
マルチコネクションにする場合は、
1 2 3 4 5 6 | void OnConnect( string hostname ) { // 相手から接続してきた場合 } void OnRecv( byte [] data, string hostname ) { // 受信データ } |
のように、sender 要素を入れておくのも良いのですが、これだと受信側がマルチにならないので、実装的には勝手にスレッドを作成して、スレッド単位で P2P 的に接続するのがよいですね。
このあたりの実装を各社さん、それぞれが作っているので、双方向プロトコル on TCP/IP として実装されたらいいなあとか、なんとか。自分で作るか?
# P2P が似たような実装をしているはずなんですが、ちょっとコードを見たことないので分からず。
高尾さんの記事
第1回 双方向通信を実現する代表的な技術
http://thinkit.co.jp/story/2011/03/01/2025