最近因為伺服器時間不同步,造成資料庫記錄時間基準不一,形成困擾。

理論上同一Domain下的主機都應會自動同步時間,但實務上偶爾就是會有出鎚的狀況。為了能快速掌握時間誤,我寫了以下的工具,可自動比對多台主機時間,將之整理成一張網頁報告,以便能快速找出伺服器時間不同步的問題。最終產出如下:

程式的運作原理是以多執行緒方式透過NetRemoteTOD API同時向多台主機取得時間。誤差判定是以計算"收到結果時間"與"結果內容時間"間差異求得(這不算精準的衡量做法,但基於取得成本低,且具有一定程度參考價值,就姑且用之),最後將結果輸出成HTML。程式碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Configuration;
using System.IO;
using System.Runtime.InteropServices;
 
namespace TimeSyncReport
{
    class Program
    {
        //時間資訊
        class TimeData
        {
            public DateTime MachineTime;
            public string TimeZone;
            public TimeSpan Difference = TimeSpan.Zero;
        }
 
        static Dictionary<string, TimeData> results
            = new Dictionary<string, TimeData>();
        static void Main(string[] args)
        {
            string[] ips = 
                ConfigurationSettings.AppSettings["ServerList"].Split(';');
            //取得伺服器清單,同時開多條執行緒執行,讀取遠端機器時間
            Thread[] workers = new Thread[ips.Length];
            for (int i = 0; i < ips.Length; i++)
            {
                string ip = ips[i];
                workers[i] = new Thread(() =>
                {
                    GetRemoteTime(ip);
                });
                workers[i].Start();
            }
            //等待所有執行緒執行完成
            for (int i = 0; i < workers.Length; i++)
                workers[i].Join();
            //將結果輸出成HTML
            StringBuilder sb = new StringBuilder();
            sb.Append(@"
<html>
  <head>
    <link rel='stylesheet' href='style.css'  type='text/css' />
  </head>
  <body>
    <table id='tblResult'>
    <tr class='hdr'>
    <td>IP</td><td>Time</td><td>TimeZone</td><td>Difference</td>
    </tr>
");
            foreach (string ip in results.Keys.OrderBy(s => s))
            {
                TimeData td = results[ip];
                sb.AppendFormat("<tr><td>{0}</td>", ip);
                //Error!
                if (td.MachineTime == DateTime.MinValue)
                    sb.AppendFormat("<td colspan='3' class='err'>{0}</td>",
                        td.TimeZone.Replace("\r", "").Replace("\n", "<br />"));
                else
                {
                    double diff = td.Difference.TotalSeconds;
                    sb.AppendFormat(
                 "<td>{0:HH:mm:ss.fff}</td><td>{1}</td><td class='{3}'>{2}</td>",
                        td.MachineTime, td.TimeZone, diff, 
                        Math.Abs(diff) > 1 ? "red" : "green");
                }
                sb.Append("</tr>");
            }
            sb.Append("</table></body></html>");
            File.WriteAllText(ConfigurationSettings.AppSettings["HtmlPath"],
                sb.ToString());
        }
        
        //REF:http://www.pinvoke.net/default.aspx/netapi32/NetRemoteTOD.html
        #region API
        [DllImport("netapi32.dll", EntryPoint = "NetRemoteTOD", 
        SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        private static extern int NetRemoteTOD(string UncServerName, 
            ref IntPtr BufferPtr);
        [DllImport("netapi32.dll")]
        private static extern void NetApiBufferFree(IntPtr bufptr);
 
        [StructLayout(LayoutKind.Sequential)]
        public struct structTIME_OF_DAY_INFO
        {
            public int itod_elapsedt;
            public int itod_msecs;
            public int itod_hours;
            public int itod_mins;
            public int itod_secs;
            public int itod_hunds;
            public int itod_timezone;
            public int itod_tinterval;
            public int itod_day;
            public int itod_month;
            public int itod_year;
            public int itod_weekday;
        }
        #endregion
 
        public static void GetRemoteTime(string ip)
        {
            try
            {
                structTIME_OF_DAY_INFO result = new structTIME_OF_DAY_INFO();
                IntPtr pintBuffer = IntPtr.Zero;
 
                // Get the time of day.
                int pintError = NetRemoteTOD(ip, ref pintBuffer);
                if (pintError > 0)
                    throw new System.ComponentModel.Win32Exception(pintError);
 
                // Get the structure.
                result = (structTIME_OF_DAY_INFO)
                    Marshal.PtrToStructure(pintBuffer,
                                           typeof(structTIME_OF_DAY_INFO));
 
                // Free the buffer.
                NetApiBufferFree(pintBuffer);
                //REF: TIME_OF_DAY_INFO 
                //http://msdn.microsoft.com/en-us/library/aa370959%28VS.85%29.aspx
                //由結果轉成DateTime物件
                DateTime dt = new DateTime(
                    result.itod_year, result.itod_month, result.itod_day,
                    result.itod_hours, result.itod_mins, result.itod_secs,
                    result.itod_hunds * 10 // 0.01秒換成0.001秒(ms)
                    );
                lock (results)
                {
                    //以收到時間值與資料內容的時間差當成誤差
                    results.Add(ip, new TimeData
                    {
                        MachineTime = dt,
                        Difference = dt - DateTime.Now.ToUniversalTime(),
                        TimeZone = result.itod_timezone.ToString()
                    });
                }
 
            }
            catch (Exception ex)
            {
                lock (results)
                {
                    //如有錯誤,將錯誤訊息寫入TimeZone
                    results.Add(ip, new TimeData
                    {
                        MachineTime = DateTime.MinValue,
                        TimeZone = ex.Message
                    });
                }
            }
        }
    }
}

Comments

# by jeff

若伺服器是使用 Windows OS,它本身就有同步時間的功能,參考下面網址。 http://ezpost.blogspot.com/2007/09/blog-post.html 另一種方式是使用 NTPClock 定時做網路校時。 http://www.stdtime.gov.tw/chinese/home.htm

# by 賣黑輪

請問我的主機是在國外它的主機時間會常常換來換去 每次我都要手動檢查然後在去改程式=.=" 我有辦法直接抓台灣的主機時間嗎 或是有這方面提供時間的網站嗎?還是有其他方式可以解決 謝謝~

Post a comment