[C#]複数のExcelのブック(シート)をマージする。

複数のExcelブックのシートをマージする。
複数のブック上のシートを1つのファイルにマージさせる方法が探しても意外と見つからずとりあえず作ってみた。
VBAであればネットに少しあるみたい。

動作環境
Windows7
Excel 2007/2010(必須)

エクセルのAPIはCOM
C#でのCOM参照は使いづらい。本来、.NetFrameworkで使用されるのが前提になっていないからなのか、APIの情報が自動ででてこない。要は式がDynamicになってしまう。
例えば、全体をtry-catchで囲っているのに、APIがdynamic型なので、次の場合、個別に対応が必要になる。

try
{
    int TabColor = 0;   // int型
    try
    {
        TabColor = ws.Tab.Color;   // エクセルAPIは有効な場合は数値、無効値は"False"が入る
    }
    catch
    {
        TabColor = 0;     // 無効値("False")がintに代入しようとしたため例外が発生する。その場合、0とする。
    }
    // 処理
}
catch
{
    // 処理
}

コツは一つのApplicationインスタンス内で全て行う。
当初、ファイル単位でApplication(Microsoft.Office.Interop.Excel.Applicationクラス)を起こして、Workbook型のインスタンスを作って、Worksheetをcopyしていたが、うまくいかず、、、。エクセルは一つのアプリケーションが起動していると、そのエクセルで次のファイルを起動する、という仕様になっていて、このエクセル内で開いているファイルならコピーができる。ということは、ひとつのApplicationインスタンス内でやってみればうまくいくんじゃないかなぁって思って、やってみたらうまくいった。

エクセルAPIを使うときの注意点。
・シートやセルのはじめのインデックスは”1″、例えば次のソースは誤り。

// A1のセルを参照
var val = ws.Cells[0,0].Value;     // [1,1]が正解

・エクセルがバックグランドで動作するので、APIを呼ぶ側のアプリケーションが異常終了するとプロセスが残る。
デバッグとかで途中で止めたり、異常終了したまま放置すると、EXCELという名前のゾンビプロセスが増殖する。
それを防ぐために、実行時に注意を促す。以下例。

if (Process.GetProcesses().Any(p => p.ProcessName.IndexOf("EXCEL") != -1))
{
    MessageBox.Show("エクセルを閉じてください!!");
    return;
}

動作画面

今回作成したソースはこちら

[C#]JSON.Netを使ってWebサービスから取得したJSONを扱うときのメモ。

Webサービスが提供するWebAPIからJSON形式で情報をもらう。
今やWebAPIなどの受け渡しに使うのはJSONが当たり前になっています。
そこで、C#でWebAPIなどからJSON形式の文字列を受信した時の扱いについてメモ。JSONの扱いにはJSON.Netを使用します。
ここでは、JSON.Netを使用してJSON文字列を連想配列により操作する方法とユーザー定義(クラス定義)に置き換える方法を試します。

JSON.Net
http://json.codeplex.com/

実際のWebサービスを使って試す。
HeartRailsExpressのWebAPIを使用して全国の鉄道情報をJSONで受け取り、C#フォームアプリケーションで使用したいと思います。

HeartRailsExpress – API
http://express.heartrails.com/api.html

今回使用するAPIは、
都道府県情報取得 API,路線情報取得 API,駅情報取得 APIの3つです。

やりたいこと。
・全国の路線、駅情報を提供するWebAPIからJSONを取得。
・WebAPIを使用して、都道府県名の列挙。
・WebAPIを使用して、都道府県別の路線情報の列挙。
・WebAPIを使用して、路線情報から駅情報の取得。
・駅情報を利用して地図上に位置を表示する。

実行画面

※このアプリケーションでは取得したJSON文字列とJSON文字列から生成したXML(XDocument)をTextBoxにそれぞれ表示しています。

連想配列によるデータの操作。
JSONの連想配列によるデータの操作はJSON.Netのデシリアライズ関数(JsonConvert.DeserializeObject関数)にNewtonsoft.Json.Linq.JContainer型を返すように指定します。

◇都道府県名の列挙。路線情報の列挙。
都道府県情報APIは次のJSONを返します。

{
    "response" : {
        "prefecture" : ["北海道","青森県",・・・,"沖縄県"]
    }
}       

JSON.Netを使ってNewtonsoft.Json.Linq.JContainer型に変換しリストボックスに表示します。
※argにはWebAPIから取得したJSON文字列が入っています。

                    var jobj = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JContainer>(arg);
                    listBoxKen.Items.AddRange(jobj["response"]["prefecture"].ToArray());

路線情報は都道府県名の列挙を行うやりかたと同じです。

                    var jobj = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JContainer>(arg);
                    listBoxRosen.Items.AddRange(jobj["response"]["line"].ToArray());

◇駅情報の列挙。
駅情報には緯度経度、前の駅名、次の駅名など様々な情報が含まれます。

{
    "response" : {
        "station" : [
             {"x":139.738535,"next":"大崎","prev":"田町","y":35.628135,"line":"JR山手線","postal":"1080075","name":"品川","prefecture":"東京都"},
             {"x":139.728246,"next":"五反田","prev":"品川","y":35.61994,"line":"JR山手線","postal":"1410032","name":"大崎","prefecture":"東京都"}
        ]
    }
}       

駅情報の中から駅名(name)だけを列挙し、リストボックスに表示します。

                    var jobj = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JContainer>(arg);
                    listBoxEki.Items.AddRange(jobj["response"]["station"].Select(p=>p["name"]).ToArray());

ユーザー定義(クラス定義)によるデータの操作。
JSON文字列をユーザー定義型に変換するにはクラス定義が必要になります。クラスを定義する際の注意すべきことはJSONのキーとクラスのプロパティ名が完全一致、JSONの値とクラスの型が変換可能である、ということが重要です。

◇クラス定義

    class MyJsonFormat
    {
        /// <summary>
        /// 都道府県情報
        /// </summary>
        public class PrefectureResponse
        {
            public class Prefecture
            {
                public string[] prefecture { get; set; }
                public Prefecture()
                {
                    prefecture = new string[] { };
                }
            }
            public Prefecture response { get; set; }

            public PrefectureResponse()
            {
                response = new Prefecture();
            }
        }

        /// <summary>
        /// 路線情報
        /// </summary>
        public class LineResponse
        {
            public class Line
            {
                public string[] line { get; set; }
                public Line()
                {
                    line = new string[] { };
                }
            }

            public Line response { get; set; }
            public LineResponse()
            {
                response = new Line();
            }
        }

        /// <summary>
        /// 駅情報
        /// </summary>
        public class StationResponse
        {
            public class Station
            {
                public double x{get;set;}
                public string next{get;set;}
                public string prev{get;set;}
                public double y { get; set; }
                public string line { get; set; }
                public string postal { get; set; }
                public string name { get; set; }
                public string prefecture { get; set; }

                public Station()
                {
                    x = y = 0;
                    next = prev = line = postal = name = prefecture = "";
                }
            }

            public class Stations
            {
                public Station[] station { get; set; }

                public Stations()
                {
                    station = new Station[] { };
                }
            }

            public Stations response { get; set; }

            public StationResponse()
            {
                response = new Stations();
            }
        }
    }

◇都道府県名の列挙。路線情報の列挙。
JSON.Netのデシリアライズ関数に定義したPrefectureResponseクラスを指定します。PrefectureResponseクラスのインスタンスから都道府県名(prefecture)を取り出し、リストボックスに表示します。

                    var jobj = JsonConvert.DeserializeObject<MyJsonFormat.PrefectureResponse>(arg);
                    listBoxKen.Items.AddRange(jobj.response.prefecture);

路線情報の列挙もJSONをLineResponseクラスへと変換し、路線名をリストボックスに表示します。

                    var jobj = JsonConvert.DeserializeObject<MyJsonFormat.LineResponse>(arg);
                    listBoxRosen.Items.AddRange(jobj.response.line);

◇駅情報の列挙。
駅情報の列挙もJSONをStationResponseクラスへと変換し、駅名(name)を取り出してリストボックスに表示します。

                    var jobj = JsonConvert.DeserializeObject<MyJsonFormat.StationResponse>(arg);
                    listBoxEki.Items.AddRange(jobj.response.station.Select(p => p.name).ToArray());

後のことを考えるなら、クラス定義を使用したほうがよい。
その場限りのコードであればわざわざクラス定義を書く必要性が無いかもしれませんが、ユーザー定義型に変換してしまったほうがデータの扱いが後々楽になります。

今回作成したソースはこちら

[C#]WebSocketを試してみる。クライアントSSL通信編。

WebSocketでSSL通信。
この投稿ではSSL通信を行うサーバーを試したのでクライアント編。
SSL通信を行うにはWebSocketサーバーへの接続URIが”ws://”ではなく”wss://”になります。

基本的にSSLの検証が通ればソース変更の必要はなし。
C#に関わらず、各WebSocketライブラリはWebSocketの規格に準拠して作られているはずなので、”SSL/TLS Support”とかライブラリのドキュメントに書いてあればSSLが使えるのだと思います。
SSLは通信が安全であるかを証明したうえで通信を行います。.NetFrameworkなどは検証を行う仕組みが存在するので、そこが通ってしまえばソースコードの改修は要らないはずです。

ちゃんとやろうとするとちょっと面倒だし、SSLを使った開発やテストなどの目的で検証を無理やり通してしまうやり方もあるので、ここでは無理やり突破してSSL通信を試します。

WebSocket4Net編
WebSocketインスタンスのAllowUnstrustedCertificateプロパティにtrueを設定します。

            var ws = new WebSocket("wss://192.168.1.22:7681/");
            ws.AllowUnstrustedCertificate = true; // 信頼されない検証を通す

System.Net.WebSockets.ClientWebSocket編
System.Net.WebSockets.ClientWebSocketはWindows8向けです。
下記の記事などを参照に通します。

@IT – SSL通信で信頼されない証明書を回避するには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/867sslavoidverify/sslavoidverify.html

接続前に検証を通すコールバックを設定します。

                    ServicePointManager.ServerCertificateValidationCallback += (s, certificate, chain, sslPolicyError)=>true; // 信頼されない検証を通す

                    await _ws.ConnectAsync(new Uri(textBox1.Text), CancellationToken.None); // WebSocketの接続

ちなみにWebSocket4Netはソケットの接続部分がラップされているので、System.Net.WebSockets.ClientWebSocketと同じ方法ではダメでした。

実行画面(左上:WebSocket4Net,左下:ClientWebSocket,右:libwebsocketsサーバー)

Windows8上でブラウザでのWebSocket(SSL)接続ができず。。。
SSL通信を無理やり通している為か、Windows8上でChrome,FireFox,IEを使用してwssによる接続を試しましたが、どれも接続完了しませんでした。
IE10に関しては無理やり突破すらできないようです。
Img20130319172309

[C#]System.Net.WebSocketsを試す。その2。サーバー編。

System.Net.WebSocketsを使用してサーバーを作成。
System.Net.WebSocketsには”WebSocketServer”とか分かりやすい名前はなく、クライアントからの待受はSystem.Net.HttpListenerクラスが行います。これはWebSocketのはじめのハンドシェイクはhttpにより行われることからですね。

詳しいことはMSDNの記事に載っています。

Windows 8 のネットワーク接続 Windows 8 と WebSocket プロトコル
http://msdn.microsoft.com/ja-jp/magazine/jj863133.aspx

上記の記事内のサンプルソースもとても参考になります。

エコーサーバーを作成してみる。
以前作成したSuperWebSocketを使用したサーバーと同じ動作をするものを作成してみます。

ソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Net;
using System.Net.WebSockets;

using System.Diagnostics;

namespace WsEchoServerForWin8
{
    class Program
    {
        static void Main(string[] args)
        {
            StartServer();
            Console.WriteLine("{0}:Server start.\nPress any key to exit.", DateTime.Now.ToString());
            Console.ReadKey();
            Parallel.ForEach(_client,p=>
            {
                if (p.State == WebSocketState.Open) p.CloseAsync(WebSocketCloseStatus.NormalClosure, "", System.Threading.CancellationToken.None);
            });
        }

        /// <summary>
        /// クライアントのWebSocketインスタンスを格納
        /// </summary>
        static List<WebSocket> _client = new List<WebSocket>();

        /// <summary>
        /// WebSocketサーバースタート
        /// </summary>
        static async void StartServer()
        {
            /// httpListenerで待ち受け
            var httpListener = new HttpListener();
            httpListener.Prefixes.Add("http://192.168.1.10:12345/");
            httpListener.Start();

            while (true)
            {
                /// 接続待機
                var listenerContext = await httpListener.GetContextAsync();
                if (listenerContext.Request.IsWebSocketRequest)
                {
                    /// httpのハンドシェイクがWebSocketならWebSocket接続開始
                    ProcessRequest(listenerContext);
                }
                else
                {
                    /// httpレスポンスを返す
                    listenerContext.Response.StatusCode = 400;
                    listenerContext.Response.Close();
                }
            }
        }
        
        /// <summary>
        /// WebSocket接続毎の処理
        /// </summary>
        /// <param name="listenerContext"></param>
        static async void ProcessRequest(HttpListenerContext listenerContext)
        {
            Console.WriteLine("{0}:New Session:{1}", DateTime.Now.ToString(), listenerContext.Request.RemoteEndPoint.Address.ToString());

            /// WebSocketの接続完了を待機してWebSocketオブジェクトを取得する
            var ws = (await listenerContext.AcceptWebSocketAsync(subProtocol:null)).WebSocket;

            /// 新規クライアントを追加
            _client.Add(ws);

            /// WebSocketの送受信ループ
            while (ws.State == WebSocketState.Open)
            {
                try
                {
                    var buff = new ArraySegment<byte>(new byte[1024]);

                    /// 受信待機
                    var ret = await ws.ReceiveAsync(buff, System.Threading.CancellationToken.None);

                    /// テキスト
                    if (ret.MessageType == WebSocketMessageType.Text)
                    {
                        Console.WriteLine("{0}:String Received:{1}", DateTime.Now.ToString(), listenerContext.Request.RemoteEndPoint.Address.ToString());
                        Console.WriteLine("Message={0}", Encoding.UTF8.GetString(buff.Take(ret.Count).ToArray()));

                        /// 各クライアントへ配信
                        Parallel.ForEach(_client,
                            p => p.SendAsync(new ArraySegment<byte>(buff.Take(ret.Count).ToArray()),
                            WebSocketMessageType.Text,
                            true,
                            System.Threading.CancellationToken.None));
                    }
                    else if(ret.MessageType == WebSocketMessageType.Close) /// クローズ
                    {
                        Console.WriteLine("{0}:Session Close:{1}", DateTime.Now.ToString(), listenerContext.Request.RemoteEndPoint.Address.ToString());
                        break;
                    }
                }
                catch
                {
                    /// 例外 クライアントが異常終了しやがった
                    Console.WriteLine("{0}:Session Abort:{1}", DateTime.Now.ToString(), listenerContext.Request.RemoteEndPoint.Address.ToString());
                    break;
                }
            }

            /// クライアントを除外する
            _client.Remove(ws);
            ws.Dispose();
            
        }
    }
}

実行画面

※実行環境はWindows8で、実行するには管理者権限が必要でした。デバッグ時にはVS自体に管理者権限を付加。ホストPC外からの接続待ちには該当Portに対してファイアーウォールの設定を行う必要がありました。

なぜArraySegmentか、という考察。
送受信時に使用しているArraySegmentについて。ArraySegmentなんて初めて使いましたが。ArraySegmentを使う理由は上部の記事(MSDN)と記事内のサンプルソースを読むと分かってきます。サンプルソースではデータ受信時の処理がループを使用し複数回に分けて送られてくることを想定して作られています。SendAsyncに指定しているendOfMessage(3つめパラメータ)が重要で、要は、大きなデータを分割して送信し、最後の送信だけendOfMessage=trueを指定することでデータ送信の終わりを受信側に知らせることができます。受信側は最後のメッセージを受信した時点で1つのデータを完成させるわけですね。ArraySegmentを使用する理由は1次配列の操作をするためだからだでしょう、楽かどうか別として。

WebSocketライブラリによって動作が違う。
文字列”Hello WebSockets World!!”を3回に分けてSendAsyncを使って送信してみます。

string s = "Hello WebSockets World!!";
var sendData = Encoding.UTF8.GetBytes(s);
await ws.SendAsync(new ArraySegment<byte>(sendData.Take(5).ToArray()), WebSocketMessageType.Text, false, System.Threading.CancellationToken.None); // endOfMessage = false
await ws.SendAsync(new ArraySegment<byte>(sendData.Skip(5).Take(5).ToArray()), WebSocketMessageType.Text, false, System.Threading.CancellationToken.None); // endOfMessage = false
await ws.SendAsync(new ArraySegment<byte>(sendData.Skip(5 + 5).ToArray()), WebSocketMessageType.Text, true, System.Threading.CancellationToken.None); // endOfMessage = true

System.Net.WebSockets.ClientWebSocketの動作。
以前作成したClientWebSocketを使用したクライアントでデータを受信した時のトレース。

Receive
Count = 5
Type  = Text
EndOfMessage= False
Msg   = Hello

Receive
Count = 5
Type  = Text
EndOfMessage= False
Msg   =  WebS

Receive
Count = 14
Type  = Text
EndOfMessage= True
Msg   = ockets World!!

ReceiveAsyncが3回呼ばれます。これらはReceiveAsyncが返す戻り値の値ですが、EndOfMessageのフラグが送信通りになっています。

しかし、WebSocket4Netを使用したクライアントでは受信処理は1度だけで送信文字列は完成された状態で受信できます。WebSocket4NetのMessageReceivedイベントで渡ってくる受信データには文字列が格納されている変数しかなく、最後のデータであるかどうかは知るすべはありません。これはWebSocketの仕様に合わせた各ライブラリの仕様上の動作だと思われますが、そのへんは使用するライブラリによりある程度はコーディングなどでフォローするしかないようです。
ちなみに、WebSocket4Netを使用したクライアントはSendAsync間にSleep(or Task.Delay)を入れてあげないと、中途半端な文字列になったりしてうまく受信できませんでした。

System.Net.WebSockets.ClientWebSocketを使用したクライアントが受信した時。
Img20130312013413
WebSocket4Netを使用したクライアントが受信した時。
Img20130312013648

今回作成したソースはこちら

[C#]非同期でSleep()したい時にはTask.Delay()を使う。

定周期処理を作るとき便利。

使い方は簡単です。
ボタンクリックしたら1秒おきにラベルに0~9までの文字を表示するプログラムを考えます。

Sleep()でやってた処理。

private async void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Run(() => System.Threading.Thread.Sleep(1000));
        label1.Text = i.ToString();
    }
}

Task.Delay()を使う場合。

private async void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(1000);
        label1.Text = i.ToString();
    }
}

実践プログラミング。指定されたIPアドレスに対して1秒周期でPingを送信して死活監視を行う。
以前、同じようなプログラムを作って、えらいめんどくさい思い出があったのですが、async/awaitを使用すると驚くほど簡単に出来ます。

実行画面

ソース

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace async_ping
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
        }

        private async void Ping(TextBox tbox)
        {
            Color setcolor = Color.WhiteSmoke;
            if (tbox.Text == "") goto CHANGECOLOR;
            try
            {
                System.Net.IPAddress ipAdr;
                var ipchk = System.Net.IPAddress.TryParse(tbox.Text,out ipAdr);
                if(ipchk ==false) goto CHANGECOLOR;

                var reply = await (new System.Net.NetworkInformation.Ping()).SendPingAsync(ipAdr, 1000);

                if (reply.Status == System.Net.NetworkInformation.IPStatus.Success)
                {
                    setcolor = Color.Lime;
                }
                else
                {
                    setcolor = Color.Red;
                }
            }
            catch
            {
                setcolor = Color.WhiteSmoke;
            }
CHANGECOLOR:
            tbox.BackColor = setcolor;
        }

        private async void Form1_Load(object sender, EventArgs e)
        {
            LoadSaveValue(isLoad: true);

            while (true)
            {
                await Task.Delay(1000);
                foreach (var ctrl in this.Controls)
                {
                    var tbox = ctrl as TextBox;
                    if (tbox != null)
                    {
                        Ping(tbox);
                    }
                }
            }
        }
        private const string SAVEFILE = "savefile.csv";
        private void LoadSaveValue(bool isLoad = false, bool isSave = false)
        {
            if (isLoad)
            {
                if(System.IO.File.Exists(SAVEFILE))
                {
                    var vals = System.IO.File.ReadAllText(SAVEFILE).Split(',');
                    textBox1.Text = vals[0];
                    textBox2.Text = vals[1];
                    textBox3.Text = vals[2];
                    textBox4.Text = vals[3];
                    textBox5.Text = vals[4];
                    textBox6.Text = vals[5];
                    textBox7.Text = vals[6];
                    textBox8.Text = vals[7];
                }
            }
            else if (isSave)
            {
                var s = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}",
                    textBox1.Text,
                    textBox2.Text,
                    textBox3.Text,
                    textBox4.Text,
                    textBox5.Text,
                    textBox6.Text,
                    textBox7.Text,
                    textBox8.Text);
                System.IO.File.WriteAllText(SAVEFILE,s);
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            LoadSaveValue(isSave: true);
        }
    }
}

[C#]System.Net.WebSocketsを試す。その1。ClientWebSocket編。

.NET Framework 4.5からサポートされているWebSocketのクライアント版。
これまでHTML5LabsのWCF WebSocketsを使用してきましたが、こちらは開発がストップ(?)しているようで、WebSocketsのプロトコルバージョンも古かったりします。もし、Windows7でWebSocketsを使用したデスクトップアプリケーションを作成する場合はSuperWebSocketsWebSocket4Netを導入するほうがよいでしょう。

本題のSystem.Net.WebSockets.ClientWebSocketですが、こちらが.NetFrameworkとして実装しているWebSocketsです。サポートOSがWindows8/WindowsServer2012となっておりWindows7はサポートしていません(コーディングはできるが、実行できない)。Windows8は持っていないので体験版を使用してVM上で試してみたい思います。

とりあえずチャットソフト。
とりあえずチャットソフトを作成。ClientWebSocketなので今回はクライアント版。サーバーはとりあえず以前作成したエコーサーバーを使用。

画面
Img20130307144817

動作画面

ソース

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Threading;
using System.Net.WebSockets;

namespace WsForm_WCF_ForWin8
{
    public partial class Form1 : Form
    {
        /// <summary>
        /// Send message buffer size.
        /// </summary>
        const int MessageBufferSize = 256;

        /// <summary>
        /// ClientWebSocket instance.
        /// </summary>
        ClientWebSocket _ws = null;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Server connect.
        /// </summary>
        private async void Connect()
        {
            if (_ws == null)
            {
                _ws = new ClientWebSocket();
            }

            if (_ws.State != WebSocketState.Open)
            {
                await _ws.ConnectAsync(new Uri(textBox1.Text), CancellationToken.None);

                while (_ws.State == WebSocketState.Open)
                {
                    var buff = new ArraySegment<byte>(new byte[MessageBufferSize]);
                    var ret = await _ws.ReceiveAsync(buff, CancellationToken.None);
                    listBox1.Items.Add((new UTF8Encoding()).GetString(buff.Take(ret.Count).ToArray()));
                    listBox1.TopIndex = listBox1.Items.Count - 1;
                }
            }
        }

        /// <summary>
        /// Connect button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            Connect();
        }

        /// <summary>
        /// Send message button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            var buff = new ArraySegment<byte>(Encoding.UTF8.GetBytes(textBox2.Text));
            if (_ws.State == WebSocketState.Open)
            {
                _ws.SendAsync(buff, WebSocketMessageType.Text, true, CancellationToken.None);
            }
        }

        /// <summary>
        /// Close websockets.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ((_ws != null) && (_ws.State == WebSocketState.Open))
            {
                _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
            }
        }
    }
}

果たしてこれで良いのかどうかわかりませんが、細かいレベルの動作についてはおいおい。。。
尚、MSDNのサンプルを参考にしました。
WebSockets middle-tier sample using ClientWebSocket
http://code.msdn.microsoft.com/WebSockets-middle-tier-5b2972ce

全然違うコーディング。
これまでやってきたC#のWebSocketsのAPI(接続、送信、受信、接続断)はイベントハンドラを利用して処理をコーディングしましたが、ClientWebSocketはasync/awaitなAPIなので、コードが全然違います。Eventと違い非同期メソッドなので、その処理を待つ・処理後のコールバックなどは自分でコーディングする必要があります。コールバック的なものはawaitの直下の処理であり、この部分は別スレッドからのUIのアクセスにInvoke()が必要ありません。但し、awaitが使用できるのはasyncメソッド内だけだったり、awaitが指定できる処理はTaskを返すメソッドである必要があったり、色々考えて作る必要がありそうです。うーん、これは慣れるのに時間がかかりそう。。。

今回作成したソースはこちら

[C#]async/awaitの使い方メモ、その1。

今後の.NET開発では必須スキルか。
.NETでの非同期処理の書き方があまりにも色々ありすぎて覚えるのも大変。でも、async/awaitを使用した非同期処理は今後重要なキーワードとなってきそうです。

参考
連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門
http://www.atmarkit.co.jp/ait/subtop/features/da/ap_masterasync_index.html

ボタンをクリックすると1秒間隔でラベルの文字が変更するプログラムを書く。
フォームのボタンをクリックすると1秒間隔で10回、ラベルの文字が変更するプログラムを考えます。

非同期処理を使わない例。
非同期処理の効用を知るために、非同期処理を使わないで処理を書いてみます。

        private void button1_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(1000);
                label1.Text = i.ToString();
            }
        }

実行画面

ボタンクリック後、処理が終わるまでコントロールはフリーズしたようになります。

非同期処理を使う例。
非同期処理をTaskクラスで実装してみます。

        private void button1_Click(object sender, EventArgs e)
        {
            var task = new TaskFactory().StartNew(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        System.Threading.Thread.Sleep(1000);
                        this.Invoke(new Action(() => label1.Text = i.ToString()));
                    }
                });
        }

実行画面

非同期処理を使うことでクリック後もコントロールの操作が可能になります。

async/awaitで非同期処理を書く例。
async/awaitを使用して非同期処理を書いてみます。

        private async void button1_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 10; i++)
            {
                await Task.Run(() => System.Threading.Thread.Sleep(1000));
                label1.Text = i.ToString();
            }
        }

メソッドの定義にasyncキーワードを追加しています。これは非同期メソッドであることを宣言しています。awaitは処理本体に記述する演算子で、単純に言えば非同期で行う処理を示しています。asyncが宣言されたメソッドにはawaitの処理が必要であり(awaitがない場合はコンパイル警告が発生)、基本的にはペアで覚えれば良いと思います。
awaitが指定できる処理にはFuncもしくはActionを実行するTaskを指定します。上記のような書き方をすれば別スレッドからのUI操作にInvokeメソッドが必要ありません。これは非同期処理が別スレッドで動作した後に、再度そのメソッドの続きから始められるようにコンパイラがしてくれているそうです。

ラムダ式の場合は次のように書きます。

        private void button1_Click(object sender, EventArgs e)
        {
            Action func = async () =>
            {
                for (int i = 0; i < 10; i++)
                {
                    await Task.Run(() => System.Threading.Thread.Sleep(1000));
                    label1.Text = i.ToString();
                }
            };

            func();
        }          

まとめ。
async/awaitの書き方はLINQやラムダ式同様、一つの糖衣構文ですね。手軽に非同期処理書けまっせということでしょう。また、WindowsPhoneやタブレットなどが増えて、ユーザーがUIに触れている時間が増えたということと、開発者がこれまで作成できなかった部分というのが広がり、UIをフリーズさせないようにするために非同期処理の重要性が高まったというのが背景にあるのかも。つまり、今後、.NETが提供する時間がかかる系のAPIはasyncメソッドなので、使い方覚えてね!ということのようです。

[C#]Microsoft Expression Encoderを使用して簡易画面キャプチャソフトを作る、その4。

簡易キャプチャソフト改良。
Formクラスの枠を使ってキャプチャ範囲を指定するという手抜きソフトですが、意外と使いやすいというwww
Img20130228204806

改良点
◇動画録画中途中キャンセル可
 これは、録画中に録画停止ボタン(右上)を右クリック、左クリック同時押しでその録画をキャンセルできるというもの。
◇録画範囲のエラーチェック
 キャプチャ対象エリアをディスプレイ外に指定された場合に強制終了する不具合をエラーを表示させるように修正。
◇キャプチャサイズ保持
 最後に使用した時のキャプチャサイズを設定ファイルに保持できるように修正。
◇簡易エンコーダー(EasyEncoder)同梱
 このキャプチャソフトは録画ファイルを00.xesc~99.xescまで作成するが、簡易エンコーダー(EasyEncoder)はこのファイルをWMVエンコードするソフト。通常は録画停止時にエンコードするが、されなかった場合用。
xescファイルと同ディレクトリ内で実行するか、xescファイルのディレクトリをコマンドライン引数で指定することでwmvへエンコードすることができる。
◇操作パネル小さくした
◇動画録画時、音声の録画とマウスカーソルのキャプチャありなしをコントロールに表示させるようにした。

既知のバグ
◇動画録画時の一時停止について
 試用版のExpression Encoderは録画開始時から10分経つと一時停止中でも構わず停止する。そのため、一時停止中に10分経ってしまい自動停止された後に新規JOBが開始され録画が始まってしまうという現象。そこまでして一時停止にこだわらないので今回はメッセージを表示して録画を停止してしまうようにした。
◇キャプチャフォームの大きさ変更不可問題
 これはキャプチャフォームの大きさを変更しているとあるところから変更できなくなる(Formの境界線に変更矢印が表示されなくなる)という致命的な問題。キャプチャフォームは録画中はWndProc()で移動不可、大きさ変更不可にしているのだが、この現象はわかんない状態。
◇設定ファイルバッティング問題
 このソフトは複数起動が可能なのだが、アプリケーション終了時に設定ファイルに設定データを保存している。なので、どっかでファイルIOがバッティングするとエラーになる。まぁ、しょうがない。

今後の改良
◇全画面キャプチャ
 全画面を対象としたキャプチャ。Expression EncoderはHD画質での録画も可能なのでWXGAのモニタ全画面もおk。
◇Expression Encoderのプリセット選択
 Expression Encoderには豊富なプリセットが用意されていてプログラム的に指定が可能だったりする。

プリセットの種類
Expression Bumper
Silverlight トレーラー
VC-1 256k DSL CBR
VC-1 256k DSL VBR
VC-1 512k DSL CBR
VC-1 512k DSL VBR
VC-1 HD 1080p VBR
VC-1 HD 720p VBR
VC-1 IIS スムーズ ストリーミング – 720p CBR
VC-1 IIS スムーズ ストリーミング – HD 1080p VBR
VC-1 IIS スムーズ ストリーミング – HD 420p VBR
VC-1 IIS スムーズ ストリーミング – SD 480p VBR
VC-1 IIS スムーズ ストリーミング – 画面エンコード VBR
VC-1 Windows Mobile
VC-1 Xbox 360 HD 1080p
VC-1 Xbox 360 HD 720p
VC-1 Zune 1
VC-1 Zune 2
VC-1 Zune 2 (AV ドック再生)
VC-1 Zune HD
VC-1 Zune HD (AV ドック再生)
VC-1 ブロードバンド CBR
VC-1 ブロードバンド VBR
VC-1 モーション サムネイル VBR
VC-1 画面エンコード VBR
VC-1 高速ブロードバンド CBR
VC-1 高速ブロードバンド VBR
WMA Lossless 5.1 オーディオ
WMA 音声オーディオ
WMA 高品質オーディオ
WMA 最良 VBR
WMA 低品質オーディオ
WMA 良質オーディオ
サンプルのオーバーレイ
ソース エンコード設定の適用
バランス
最速
最良

エンコード実施時のプリセットの指定

var job = new Job();
job.MediaItems.Add(item);

// プリセットの設定
job.ApplyPreset(Presets.AACGoodQualityAudio);

// エンコードの開始
job.Encode();

プリセットを指定することで出力されるファイルがそれぞれに応じた音声コーデック、画面の大きさに変更されるようになる。

Expression Encoder4自体のソフトを見ているとかなりカスタマイズが可能なようなので、プログラムレベルでこれらが指定できるのは非常にありがたい。うーん、本当に開発が終わってしまうのが惜しい。
Img20130228205755

今回作成したソースはこちら

[C#]WebSocketを試してみる。サーバーへの自動再接続機能を実装するの巻。

引き続きWebSocket。

クライアント側のサーバーから切断されてしまった時の再接続機能をさくっと作ってみた。
基本的にはクローズイベントかエラー発生のイベントで再接続をすれば良い。ただ、APIによって仕様が違ったりする。将来的には実装されるAPIまで標準化されることでしょう。

ここでは画面上にサーバーが送信してくる時刻を常に表示し続けるクライアントを作成します。
画面Img20130218153954

C# – WCF WebSockets版

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.ServiceModel.WebSockets;

namespace WsForm_WCF
{
    public partial class Form1 : Form
    {

        /// <summary>
        /// WebSocketsインスタンス
        /// </summary>
        private WebSocket _ws = null;

        /// <summary>
        /// リトライカウンタ
        /// </summary>
        private int _retryCount = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            /// サーバー接続開始
            Connect();
        }

        /// <summary>
        /// サーバーへ接続する
        /// </summary>
        private void Connect()
        {

            /// listBoxへ文字列を挿入する。
            Action<string> AddText = (s) =>
                {
                    listBox1.Items.Add(s);
                    listBox1.TopIndex = listBox1.Items.Count - 1;
                };

            if (_ws == null)
            {
                AddText("サーバー接続を開始します。");
            }
            _ws = new WebSocket("ws://192.168.1.2:12345");

            /// 文字列受信
            _ws.OnData += (s, e) =>
            {
                AddText(e.TextData);
            };

            /// サーバー接続完了
            _ws.OnOpen += (s, e) =>
            {
                _retryCount = 0;
                AddText("サーバーに接続しました。");
            };

            /// 接続断の発生
            _ws.OnClose += (s, e) =>
            {
                AddText("サーバー接続中..リトライ" + (++_retryCount).ToString() + "回目");

                /// 再接続を試行する
                Connect();
            };

            /// サーバー接続開始
            _ws.Open();

        }
    }
}

C# – WebSocket4Net版

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using WebSocket4Net;

namespace WsForm_WebSocket4Net
{
    public partial class Form1 : Form
    {
        /// <summary>
        /// WebSocketsインスタンス
        /// </summary>
        private WebSocket _ws = null;

        /// <summary>
        /// リトライカウンタ
        /// </summary>
        private int _retryCount = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            /// サーバー接続開始
            Connect();
        }

        /// <summary>
        /// サーバーへ接続する
        /// </summary>
        private void Connect()
        {

            /// listBoxへ文字列を挿入する。
            Action<string> AddText = (s) =>
            {
                this.Invoke(new Action(() =>
                    {
                        listBox1.Items.Add(s);
                        listBox1.TopIndex = listBox1.Items.Count - 1;
                    }));
            };

            if (_ws == null)
            {
                AddText("サーバー接続を開始します。");
            }
            _ws = new WebSocket("ws://192.168.1.2:12345");

            /// 文字列受信
            _ws.MessageReceived += (s, e) =>
            {
                AddText(e.Message);
            };

            /// サーバー接続完了
            _ws.Opened += (s, e) =>
            {
                _retryCount = 0;
                AddText("サーバーに接続しました。");
            };

            /// 接続断の発生
            _ws.Error += (s,e)=>
            {
                AddText("サーバー接続中..リトライ" + (++_retryCount).ToString() + "回目");
                /// 再接続を試行する
                Connect();
            };

            /// 接続断の発生
            _ws.Closed += (s, e) =>
            {
                AddText("サーバー接続中..リトライ" + (++_retryCount).ToString() + "回目");
                /// 再接続を試行する
                Connect();
            };

            /// サーバー接続開始
            _ws.Open();

        }
    }
}

WCF WebSocketsとWebSocket4Netの違いはほぼないものの、エラーハンドリングがあるだけWebSocket4Netはちょっと分かりやすいかもしれません。
WCF WebSocketsのエラー内容はOnClose()のsenderから得ることができます。

            ws.OnClose += (s, e) =>
            {
                Console.WriteLine("Last Error:{0}.", ((WebSocket)s).LastError.ToString());
            };

実行画面

気になったのはエラー発生後、サーバーへの接続を行うsleepは指定していませんが、どれも1秒おきくらいにやっているように見えることです。ちなみに、ブラウザ(Javascript版)では実行環境の違いによって試行までの時間(他のPC環境では10秒おきくらい)が違ったりしました。

おまけ
JavaScript版

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>WebSocket Sample</title>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>WebSocket アプリケーション</h1>
            </div>
        </header>
		<div id="log"></div>
		<script type="text/javascript">
			var ws;
			var retryCount = 0;
			var logCount = 0;
			var connect = function(){
				
				if ("WebSocket" in window) {
					ws = new WebSocket("ws://192.168.1.2:12345");
				} else if ("MozWebSocket" in window) {
					ws = new MozWebSocket("ws://192.168.1.2:12345");
				}

				ws.onmessage = function(event){
				    output(event.data);
				}
				
				if( retryCount > 0 ){
					output("サーバー接続中..リトライ" + retryCount.toString() + "回目");
				}

				
				ws.onopen = function(event){
				    output("サーバーに接続しました。");
				    retryCount = 0;
				}

				ws.onclose = function(event){
					retryCount++;
					connect();
				}

				function output(str){
					document.getElementById("log").innerHTML += str + "<hr />";
					logCount++;
					scroll(0,logCount*100);
				}

				function disconnect(){
					ws.close();
					ws = null;
				}
			}
			
			connect();
		</script>
        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

[C#]WebSocketを試してみる。お手軽シンプルブロードキャストエコーサーバー作成の巻。

ブロードキャストエコーサーバー
WebSocketのテスト用。
送信時にちょっと工夫。ユーザーに送信する際にParallel.ForEach()を使用して並列化してみた。これを使うと複数ユーザーに送信する処理を並列化できる。例えば大きいデータなどを分割して送りたい処理などに有効。但し、リソースの消費量が大きくなるので注意が必要。Parallel.ForEach()はマルチコアCPUをフル活用するからパソコンのファンがブーンって鳴る。

並列化の効用。
複数の画像を各クライアントにプッシュ配信するサーバーの例。合計1.77MBの画像データを8KBパケットに分割して送信するとします。実行画面では1フォルダにつき1クライアントが動作しています。

以下、シンプルブロードキャストサーバーのソース。
サーバーソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
using SuperWebSocket;

namespace WsEchoServer
{
    class Program
    {
        static void Main(string[] args)
        {
            /// WebSocket初期化
            var server = new SuperWebSocket.WebSocketServer();
            var rootConfig = new SuperSocket.SocketBase.Config.RootConfig();
            var serverConfig = new SuperSocket.SocketBase.Config.ServerConfig()
            {
                Port = 12345,
                Ip = "Any",
                MaxConnectionNumber = 100,       /// 最大ユーザセッション数
                Mode = SocketMode.Sync,
                Name = "Echo Server"
            };

            /// クライアントセッションを格納
            var clinet_sessions = new List<WebSocketSession>();

            /// セッションの接続
            server.NewSessionConnected += (s) =>
                {
                    Console.WriteLine("{0}:New Session:{1}", DateTime.Now.ToString(), s.RemoteEndPoint.Address.ToString());

                    /// 新クライアントとして格納
                    clinet_sessions.Add(s);
                };

            /// セッションのクローズ
            server.SessionClosed += (s, r) =>
                {
                    Console.WriteLine("{0}:Session Close:{1}", DateTime.Now.ToString(), s.RemoteEndPoint.Address.ToString());

                    /// 該当クライアントの除外
                    clinet_sessions.Remove(s);
                };

            /// データ受信(バイナリ)
            server.NewDataReceived += (s, d) =>
                {
                    Console.WriteLine("{0}:Binary Received:{1}", DateTime.Now.ToString(), s.RemoteEndPoint.Address.ToString());
                    Console.WriteLine("Length={0}", d.Length);
                    
                    /// 全ユーザに送信
                    Parallel.ForEach(clinet_sessions, p => p.SendResponse(d));
                };

            /// データ受信(文字列)
            server.NewMessageReceived += (s, m) =>
                {
                    Console.WriteLine("{0}:String Received:{1}",DateTime.Now.ToString(), s.RemoteEndPoint.Address.ToString());
                    Console.WriteLine("Message={0}", m);

                    /// 全ユーザに送信
                    Parallel.ForEach(clinet_sessions, p => p.SendResponse(m));
                };


            /// サーバセットアップ
            server.Setup(rootConfig, serverConfig, SocketServerFactory.Instance);
            /// サーバ起動
            server.Start();

            Console.WriteLine("{0}:Server start.",DateTime.Now.ToString());
        }
    }
}

クライアントにはWebSocket4Netを使用。
これまでクライアントにはWCF WebSocketsを使っていたけど、SuperWebSocketsに含まれていたWebSocket4Netを使用してみる。
ほぼWCF WebSocketsと使用感は変わらない。

WebSocket4Net
http://websocket4net.codeplex.com/

クライアントソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using WebSocket4Net;

namespace WsClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var ws = new WebSocket("ws://192.168.1.2:12345");

            /// 文字列受信
            ws.MessageReceived += (s, e) =>
                {
                    Console.WriteLine("{0}:String Received:{1}", DateTime.Now.ToString(), e.Message);
                };

            /// バイナリ受信
            ws.DataReceived +=(s,e)=>
                {
                    Console.WriteLine("{0}:Binary Received Length:{1}", DateTime.Now.ToString(), e.Data.Length);
                };

            /// サーバ接続完了
            ws.Opened += (s, e) =>
            {
                Console.WriteLine("{0}:Server connected.", DateTime.Now.ToString());
            };

            /// サーバ接続開始
            ws.Open();

            /// 送受信ループ
            while (true)
            {
                var str = Console.ReadLine();
                if (str == "END") break;

                if (ws.State == WebSocketState.Open)
                {
                    ws.Send(str);
                }
                else
                {
                    Console.WriteLine("{0}:wait...", DateTime.Now.ToString());
                }
            }


            /// ソケットを閉じる
            ws.Close();

        }
    }
}

実行画面

今回作成したソースはこちら