2014/08/10

C#.Net 撰寫一個能夠同步時間的Windows Service

這次我第四號桌機因為水銀電池沒電造成開機時間會恢復到主機板出廠值,所以買了水銀電池裝上去過幾天一樣也不會記憶,還花了我80元
早知道拿去買便當了,有點傷本Orz

這一個月我要讓時間同步的方法有以下幾種:
  1. 進去BIOS設定
  2. 在開機時透過時鐘同步的更能實現


今天睡醒時,真的受不了在繼續這樣下去了…
就直接寫一個Service在開機時透過網路與NTP Server取得時間並設定在系統上

首先開啟一個專案,選擇Windows服務

在Service1的設計畫面點右鍵/加入安裝程式



ProjectInstaller設計畫面去設定serviceInstaller1屬性




DelayedAutoStart屬性設定為true,使得在開機時延遲自動執行,延遲是怕剛開機時沒有網路服務,所以特別讓他能等待一會後在開始同步進行更新
StartType:設定為Automatic,讓開機時自動執行


接著設定serviceProcessInstaller1的屬性

將Account選為LocalSystem

將EventLog的元件拖曳到Service1的設計畫面上


並複製以下程式碼貼到Service1.cs和NTPClient.cs
不要按下F6(Run Project)會有錯誤,請按下按下F6(Build Project) VS會對專案進行建置

Service1.cs:
using System.ServiceProcess;
using System.Threading;

namespace MyUpdateTimeService
{
    public partial class MyUpdateService : ServiceBase
    {
        private NTPClient ntpClient;
        const string source = "UpdateTimeService";

        public MyUpdateService()
        {
            InitializeComponent();
            this.AutoLog = false;
            if (!System.Diagnostics.EventLog.SourceExists(source))
            {
                System.Diagnostics.EventLog.CreateEventSource(source, "UpdateTimeLog");
            }

            eventLog1.Source = source;
            ntpClient = new NTPClient();
        }

        protected override void OnStart(string[] args)
        {

            eventLog1.WriteEntry("Start");

            bool isSet = ntpClient.GetNtpTime();
            if (isSet)
            {
                eventLog1.WriteEntry("true");
                this.OnPause();
            }
            else
                eventLog1.WriteEntry("false");
            
            Thread.Sleep(1000);
        }

        protected override void OnStop()
        {
            eventLog1.WriteEntry("Stop");
        }
    }
}


NTPClient.cs:
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace MyUpdateTimeService
{
    public class NTPClient
    {
        protected internal bool GetNtpTime()
        {
            const string ntpServer = "time.nist.gov";
            byte[] ntpData = new byte[48];

            //LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode)
            ntpData[0] = 0x1B;

            IPAddress[] addresses = Dns.GetHostEntry(ntpServer).AddressList;
            IPEndPoint ipEndPoint = new IPEndPoint(addresses[0], 123);
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            socket.Connect(ipEndPoint);
            socket.Send(ntpData);
            socket.Receive(ntpData);
            socket.Close();


            const byte serverReplyTime = 40;
            //Get the seconds part
            ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);

            //Get the seconds fraction
            ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);

            //Convert From big-endian to little-endian
            intPart = SwapEndianness(intPart);
            fractPart = SwapEndianness(fractPart);

            var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

            //UTC time + 8 
            DateTime networkDateTime = (new DateTime(1900, 1, 1))
                .AddMilliseconds((long)milliseconds).AddHours(8);

            if (UpdateTime.SetTime(networkDateTime))
                return true;
            return false;
        }

        // stackoverflow.com/a/3294698/162671
        static uint SwapEndianness(ulong x)
        {
            return (uint)(((x & 0x000000ff) << 24) +
                           ((x & 0x0000ff00) << 8) +
                           ((x & 0x00ff0000) >> 8) +
                           ((x & 0xff000000) >> 24));
        }
        public class UpdateTime
        {
            [DllImport("kernel32.dll")]
            private static extern bool SetLocalTime(ref TIME time);

            [StructLayout(LayoutKind.Sequential)]
            private struct TIME
            {
                public short year;
                public short month;
                public short dayOfWeek;
                public short day;
                public short hour;
                public short minute;
                public short second;
                public short milliseconds;
            }


            protected internal static bool SetTime(DateTime dt)
            {
                TIME time;
                time.year = (short)dt.Year;
                time.month = (short)dt.Month;
                time.dayOfWeek = (short)dt.DayOfWeek;
                time.day = (short)dt.Day;
                time.hour = (short)(dt.Hour);
                time.minute = (short)dt.Minute;
                time.second = (short)dt.Second;
                time.milliseconds = (short)dt.Millisecond;
                return SetLocalTime(ref time);
            }
        }

    }
}

接著用管理者權限開啟終端機,不用沒辦法將服務安裝上去



並切換到專案的Release資料夾底下輸入指令
#安裝服務
installutil 服務名稱.exe

#移除服務
installutil /u 服務名稱.exe


安裝成功的畫面如下:


解除安裝的畫面如下:


安裝完成後,可以在『控制台/系統及安全性/系統管理工具/服務』看到剛剛安裝上去的Service


如果想查看執行Log,可以在『控制台/系統及安全性/系統管理工具/事件檢視器/Windows紀錄/硬應用程式』看到



現在可以選擇要手動將該服務開啟,或是重開機自動會執行該服務
寫完程式以及文章整個好餓XD


參考資料:
http://stackoverflow.com/questions/1193955/how-to-query-an-ntp-server-using-c
http://pastebin.com/KrDSTz4J
http://msdn.microsoft.com/zh-tw/library/ms172517(v=vs.90).aspx
http://j796160836.pixnet.net/blog/post/40306051
http://www.dotblogs.com.tw/kirkchen/archive/2010/04/30/14943.aspx