TopShelfを使ってWindowsサービスを作る

#はじめに

これまで何回かWindowsサービスをつくる機会がありましたが、
そのたび「デバッグ環境を整えるのめんどくさいなぁ」と思っていました。
通常はサービスプロセスにデバッガをアタッチさせてなんたらかんたらという手順が必要です。

しかし最近「[TopShelf](https://github.com/Topshelf/Topshelf)」(Apache License 2.0)というライブラリの存在を知りました。
使ってみると、私はもうWindowsサービスの開発にTopShelfを手放せなくなりました。

ざっくりメリットを言うと、

* コンソールアプリとして作成するので、開発時はF5キーで普通にデバッグできる
* サービスインストールがコマンド一発で完了

とくにデバッグ手順が簡単なのがうれしい。
これだけで惚れました。

#TopShelfを使ってソケットサーバーをつくる

ここでは、TopShelfを使ってソケットサーバーをつくってみます。

##準備

まずはコンソールアプリケーションを新規作成します。
次に、NuGetでもGitHubからでもいいので、TopShelf.dllを落としてきて参照させます。

##ソケットサーバークラスを作成

```csharp:SocketServer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace TopShelfSample
{
    public class SocketServer
    {
        private TcpListener tcpListener;
        private readonly System.Timers.Timer timer;

        private readonly int codepageEUC = 51932;
        private readonly int codepageShiftJIS = 932;
        private readonly int codepageUTF8 = 65001;

        private readonly byte EOT = 0x04;
        private readonly byte ACK = 0x06;
        private readonly byte NAK = 0x15;

        // サービス開始/停止
        public void Start() { timer.Start(); }
        public void Stop() { timer.Stop(); }

        public SocketServer()
        {
            tcpListener = new TcpListener(IPAddress.Any, 8210);
            tcpListener.Start();

            timer = new System.Timers.Timer(1000)
            {
                AutoReset = true
            };
            timer.Elapsed += (sender, eventArgs) => checkRequest();
        }

        /// <summary>
        /// リクエストチェック
        /// </summary>
        private void checkRequest() {
            Console.WriteLine("Check Request...");
            if (tcpListener.Pending() == true)
            {
                Thread threadReceive = new Thread(new ThreadStart(threadExecute));
                threadReceive.Start();
            }
        }

        /// <summary>
        /// スレッド処理
        /// </summary>
        private void threadExecute()
        {
            // 接続要求を受け入れ
            TcpClient tcp = tcpListener.AcceptTcpClient();
            Socket sckt = tcp.Client;

            NetworkStream netStream = tcp.GetStream();
            MemoryStream memoryStream = new MemoryStream();

            try
            {
                bool eot = false;
                byte[] buffer = new byte[1];
                int recieveSize;

                do
                {
                    recieveSize = netStream.Read(buffer, 0, buffer.Length);
                    if (buffer[0].CompareTo(this.EOT) == 0)
                    {
                        eot = true;
                    }
                    else
                    {
                        memoryStream.Write(buffer, 0, recieveSize);
                    }
                } while (netStream.DataAvailable);

                memoryStream.Seek(0, SeekOrigin.Begin);
                byte[] rcvData = memoryStream.ToArray();

                // EOTがきたか
                byte[] replyPacket = new byte[1];
                if (eot)
                {
                    replyPacket[0] = this.ACK;
                    netStream.Write(replyPacket, 0, 1);
                    string rcvString = Encoding.GetEncoding(this.codepageShiftJIS).GetString(rcvData);

                    // この後、必要な処理
                    DoSomething ds = new DoSomething();
                    ds.DoSomething(rcvString);
                }
                else
                {
                    // EOTが来なかった場合は異常終了とする
                    replyPacket[0] = this.NAK;
                    netStream.Write(replyPacket, 0, 1);
                }

            }
            catch (Exception ex)
            {
                Console.Write("Exception!");
            }
            finally
            {
                if (memoryStream != null)
                {
                    memoryStream.Close();
                    memoryStream.Dispose();
                }
                if (netStream != null)
                {
                    netStream.Close();
                    netStream.Dispose();
                }
                if (tcp != null)
                {
                    tcp.Close();
                }
            }
        }
    }
}
```


##Program.csに以下のように記述

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Topshelf;

namespace TopShelfSample
{
    class Program
    {
        static void Main(string[] args)
        {
            HostFactory.Run(x =>
            {
                x.Service<SocketServer>(s =>
                {
                    s.ConstructUsing(name => new SocketServer());
                    s.WhenStarted(tc => tc.Start());
                    s.WhenStopped(tc => tc.Stop());
                });

                //Windowsサービスの設定
                x.RunAsLocalSystem();
                x.SetDescription("This is TopShelfSample");
                x.SetDisplayName("TopShelfSample");
                x.SetServiceName("TopShelfSample_Service");
            });
        }
    }
}
```


コンソールアプリケーションとして作成しているので、ブレークポイントを適当につけてF5実行すれば、コンソールアプリとして普通にデバッグできます。

#Windowsサービスとしてインストール

Windowsサービスとしてインストールさせる場合は、ビルド後のexeに対して以下のようにコマンドを実行するだけです。

```
> TopShelfSample.exe install
```

#まとめ

開発時はコンソールアプリケーションとして通常の手順でデバッグができ、
Windowsサービスとしてインストールする場合にはコマンド一発で完了できます。

#参考
[Windowsサービスを楽に開発～TopShelf～](http://84zume.wordpress.com/2014/03/23/topshelf/)