normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

Silverlight で WebSocket を利用する

既に id:shiba-yan がTIPS id:shiba-yan:20111101:1320075209 を紹介している WebSocket だが、Silverlight からも利用できることをご存じだろうか。現在は HTML5Labs で試験的に公開されているのみだが、当該ランタイムをインストールすることで IE9Silverlight からも WebSocket を利用することが可能となる。
WebSocket 自体の利用方法については、 以下の記事が参考になるが、Silverlight から WebSocket を利用する方法については言及していない。

今回は、 Silverlight から WebSocket を利用する方法を紹介する。以下のように、Silverlight とブラウザ間で相互に PUSH 通信を行うことが可能となる。

ランタイムのインストール

以下のサイトから wcfwebsockets.msi を取得し、インストーラに従ってランタイムをインストールする。

インストールが完了すると「C:\Program Files (x86)\Microsoft SDKs\WCF WebSockets\11.06.22\bin」にDLLが配置される。以下の画像を参考にして頂きたい。

サーバ側の実装

まず、ASP.NETのプロジェクトに対し、以下の2アセンブリについての参照を追加する。

  • Microsoft.ServiceModel.WebSockets
  • System.ServiceModel

次に、id:shiba-yan:20111101:1320075209 の実装例を参考にして、ASP.NET側に Global.asax を追加して以下を記述する。

  • Global.asax
using System;
using System.Collections.Generic;
using Microsoft.ServiceModel.WebSockets;
using System.ServiceModel;

namespace SLWebSocket.Web
{
    public class Global : System.Web.HttpApplication
    {
        WebSocketsHost<WebSocketsDemo.WebSocketsDemo> ws;

        protected void Application_Start(object sender, EventArgs e)
        {
            // サービスが公開されるアドレス、ポート番号
            var uri = new Uri("ws://localhost:4503/chat");

            // URI とサービスクラスを渡して、ホストを初期化
            ws = new WebSocketsHost<WebSocketsDemo.WebSocketsDemo>(uri);

            // WebSocket のエンドポイントを追加して、接続を待機
            ws.AddWebSocketsEndpoint();
            ws.Open();
        }

        protected void Application_End(object sender, EventArgs e)
        {
            ws.Close();
        }
    }
}

namespace WebSocketsDemo
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class WebSocketsDemo : WebSocketsService
    {
        private static readonly List<WebSocketsDemo> _sessions = new List<WebSocketsDemo>();

        // WebSocket セッションが開始された時に呼ばれる
        public override void OnOpen()
        {
            // セッションをリストに追加
            _sessions.Add(this);
        }

        // WebSocket セッションが閉じられた時に呼ばれる
        protected override void OnClose(object sender, EventArgs e)
        {
            // セッションをリストから削除
            _sessions.Remove(this);
        }

        // クライアントからのメッセージを受信した時に呼ばれる
        public override void OnMessage(string value)
        {
            // 登録されているセッションに対してメッセージを送信
            foreach (var session in _sessions)
            {
                session.SendMessage(value);
            }
        }
    }
}

ここで、利用するポートを 4503 にしている点に注意してほしい。これは、WebSocketのプロトタイプ実装では4502-4534 ポートしか利用できないのが原因だ。実装の際には留意が必要となるので、本件以外にもWebSocketプロトタイプ実装の制限や利用手順等を記述した、以下のサイトを参照して欲しい。

クライアント側の実装

ASP.NETプロジェクト同様、作成したSilverlightプロジェクトに対し、まず次のアセンブリを追加する。

次に、以下のソースを追記する。特に注意点は無いので、ソースコードを参照して頂きたい。

<UserControl x:Class="SLWebSocket.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="メッセージ送信" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="95" Click="button1_Click" />
        <ListBox Height="247" HorizontalAlignment="Left" Margin="12,41,0,0" Name="listBox1" VerticalAlignment="Top" Width="376" />
        <TextBox Height="24" HorizontalAlignment="Left" Margin="113,11,0,0" Name="textBox1" VerticalAlignment="Top" Width="275" Text="メッセージ♪" />
    </Grid>
</UserControl>
using System;
using System.Windows;
using System.Windows.Controls;
using System.ServiceModel.WebSockets;

namespace SLWebSocket
{
    public partial class MainPage : UserControl
    {
        WebSocket ws = null;

        public MainPage()
        {
            InitializeComponent();

            // サービス側のURIを指定してインスタンスを作成
            ws = new WebSocket("ws://localhost:4503/chat");

            // イベントを登録してソケットをオープン
            ws.OnOpen += new EventHandler<EventArgs>(ws_OnOpen);
            ws.OnData += new EventHandler<WebSocketEventArgs>(ws_OnData);
            ws.Open();
        }

        void ws_OnData(object sender, WebSocketEventArgs e)
        {
            listBox1.Items.Insert(0, e.TextData);
        }

        void ws_OnOpen(object sender, EventArgs e)
        {
            ws.SendMessage("Silverlight Appのソケットオープン" + DateTime.Now);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            if (ws.ReadyState != WebSocketState.Closed)
            {
                ws.SendMessage(textBox1.Text + " " + DateTime.Now);
            }
            else
            {
                listBox1.Items.Insert(0, "ソケットが閉じてます");
            }
        }
    }
}

clientaccesspolicy.xml の配置

サーバ側実装とクライアント側実装は完了したが、このままでは Silverlightからの WebSocketを利用した疎通ができため、 clientacesspolicy.xml を配置する必要がある。

配置場所に注意が必要であり、ローカルの開発サーバで実行する場合、ローカルIISのルートに以下の clientacesspolicy.xml を配置する必要がある*1

  • clientacesspolicy.xml
<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*" />
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true" />
        <socket-resource port="4502-4530" protocol="tcp" />
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Windows 7 の環境での配置例については、以下の画像を参考にして頂きたい。

上記の完了後、サンプルを実行すると無事疎通が行えるはずだ。

*1:ASP.NETプロジェクト側のルートではうまく動作しない