Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

はじめに

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

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

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

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

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

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

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

準備

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

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

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に以下のように記述

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~

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away