システム開発はGWave-Systemsにお任せください。

gwave-systems.com
技術情報
カテゴリ別技術情報
SNTP (C#.Net) Shutdown (C#.Net) WakeUp On LAN (Perl) DDE (C#.Net)

SNTP (C#.Net)      ▲ページの先頭に戻る
SNTP … Simple Network Time Protocol の略です。ネットワーク上に存在するタイムサーバと時刻データの受け渡しを行うプロトコルです。 NTPの詳細についてはインターネット上で多く解説されていますので、そちらをご覧ください。

以下に、C#.Netによる実装方法を簡単に示します。

1. NTPクラスを作成します。

2. 実行用クラスを作成します。
NTPクラス生成時にNTPサーバを指定し、GetCurrentTime()を呼ぶと最新の時刻を取得できます。

※取得した時刻を使用してWindowsのシステム日付を更新したい場合は、Win32APIのSetLocalTime/SetSystemTimeを使用します。


1. NTPクラス。
public class NtpClient {

/// <summary>
/// NTPパケットを格納する構造体
/// </summary>
private struct NTP_Packet {
public int Control_Word;
public int root_delay;
public int root_dispersion;
public int reference_identifier;
public Int64 reference_timestamp;
public Int64 originate_timestamp;
public Int64 receive_timestamp;
public int transmit_timestamp_seconds;
public int transmit_timestamp_fractions;
};

/// <summary>
/// 時刻問い合わせ先サーバ名(FQDN)
/// </summary>
private string _server;

/// <summary>
/// コンストラクタ: インスタンス生成時にNTPサーバ(FQDN)を設定します
/// </summary>
/// <param name="server">NTPサーバ名(FQDN)</param>
public NtpClient(string server) {
_server = server;
}

/// <summary>
/// NTP_Packet内のデータ全てをByte配列として取得します
/// </summary>
/// <param name="strct">NTPサーバから取得したデータを格納しているNTP_Packet</param>
/// <returns>NTPパケットデータをByte配列に変換したデータ</returns>
private byte[] convertStruct2Bytes(NTP_Packet strct) {
// 各項目をbyteデータに変換します。
byte[] dat1 = BitConverter.GetBytes(strct.Control_Word);
byte[] dat2 = BitConverter.GetBytes(strct.root_delay);
byte[] dat3 = BitConverter.GetBytes(strct.root_dispersion);
byte[] dat4 = BitConverter.GetBytes(strct.reference_identifier);
byte[] dat5 = BitConverter.GetBytes(strct.reference_timestamp);
byte[] dat6 = BitConverter.GetBytes(strct.originate_timestamp);
byte[] dat7 = BitConverter.GetBytes(strct.receive_timestamp);
byte[] dat8 = BitConverter.GetBytes(strct.transmit_timestamp_seconds);
byte[] dat9 = BitConverter.GetBytes(strct.transmit_timestamp_fractions);

// 全項目を連結してByte列を生成します。
byte[] data = appendBytes(dat1, dat2);
data = appendBytes(data, dat3);
data = appendBytes(data, dat4);
data = appendBytes(data, dat5);
data = appendBytes(data, dat6);
data = appendBytes(data, dat7);
data = appendBytes(data, dat8);
data = appendBytes(data, dat9);
return data;
}

/// <summary>
/// byte配列の後ろに、byte配列を追加します
/// </summary>
/// <param name="data">追加先byte配列</param>
/// <param name="add">追加用byte配列</param>
/// <returns>新しい配列データ</returns>
private byte[] appendBytes(byte[] data, byte[] add) {
int len = data.Length + add.Length;
byte[] newdata = new byte[len];
int idx = 0;
for (int i = 0; i < data.Length; i++) {
newdata[idx++] = data[i];
}
for (int i = 0; i < add.Length; i++) {
newdata[idx++] = add[i];
}
return newdata;
}

/// <summary>
/// NTPサーバから現在の時刻を取得します
/// </summary>
/// <param name="newTime">取得した現在の時刻</param>
/// <returns>true:処理成功時、false:処理失敗時</returns>
public bool GetCurrentTime(ref DateTime newTime) {
bool ret = false;
DateTime dtTime = DateTime.Now;
try {
// IPAddress、IPEndPointは、リクエストを受け取る為のEND-POINTを表します。
// DNSから指定したNTPサーバのIPアドレスを取得します。
IPHostEntry list = System.Net.Dns.GetHostEntry(_server);
IPAddress remoteAddr = list.AddressList[0];
WriteLog("Server IP " + remoteAddr.ToString());
IPEndPoint remotePoint = new IPEndPoint(remoteAddr, 123);
EndPoint remoteEnd = (EndPoint)remotePoint;

// +αTips
// WindowsXPが使用している 123のポートから投げて123で取得する方法。
// サービスの「Windows Time」を停止しておきます。123使用中のPGを停止します。
// 停止していないと、エラー「"通常、各ソケット アドレスに対してプロトコル、ネットワーク アドレス、またはポートのどれか 1 つのみを使用できます。"」となります。
// 因みに2005でも2002でも同じ。
string localIpStr = "127.0.0.1";
IPAddress localAddr = System.Net.Dns.GetHostEntry(localIpStr).AddressList[0];
WriteLog("Local IP " + localAddr.ToString());
IPEndPoint localPoint = new IPEndPoint(localAddr, 0);
WriteLog("Local IP " + localPoint.ToString());
EndPoint senderPoint = (EndPoint)localPoint;

// UDPを使用して通信します。(ソケット生成オプション:UDPの場合はSocketType.Dgram)
Socket s = new Socket(remotePoint.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
// タイムアウトを設定します(ミリ秒単位で最小500ミリ秒から。0,-1は無限)
s.SendTimeout = 1000 * 10;
s.ReceiveTimeout = 1000 * 10;
// ローカルIPポートをソケットにバインド (→ ただし、開発環境が無いPCではエラーになってしまうのでbindしません...)
//s.Bind(localPoint);

// NTPサーバに時刻取得用コマンドリクエストを送信します。
NTP_Packet dat;
dat.Control_Word = 0xb;
dat.root_delay = 0;
dat.root_dispersion = 0;
dat.reference_identifier = 0;
dat.reference_timestamp = 0;
dat.originate_timestamp = 0;
dat.receive_timestamp = 0;
dat.transmit_timestamp_seconds = 0;
dat.transmit_timestamp_fractions = 0;
byte[] data = convertStruct2Bytes(dat);
int i = s.SendTo(data, SocketFlags.None, remotePoint);
WriteLog("Sent " + i + " bytes.");

// NTPサーバから時刻データを取得します。
Int32 bytes = 0;
Byte[] recvBytes = new Byte[50];
bytes = s.ReceiveFrom(recvBytes, SocketFlags.None, ref senderPoint);
s.Close();
WriteLog("Receive: " + bytes + " bytes.");

// ----------------------------------------------------------------------
// NTPサーバから取得したデータから現在日時を計算します。
// ----------------------------------------------------------------------
if (bytes > 0) {
// 1970/1/1からの日数
double cuDays;
// 現在の時刻(何時か)
double cuHours;
// 現在の分
double cuMinutes;
// 時間算出用
double cuTempSecs;
//
double cuTimeStamp;

// 世界協定時刻時間に変換します。40-43バイト目で判別、ms単位は無視
double d1 = double.Parse("" + recvBytes[40]);
double d2 = double.Parse("" + recvBytes[41]);
double d3 = double.Parse("" + recvBytes[42]);
double d4 = double.Parse("" + recvBytes[43]);
cuTimeStamp = (double)(d1 * Math.Pow(2, 8 * 3) + d2 * Math.Pow(2, 8 * 2) + d3 * Math.Pow(2, 8) + d4);

// NTPサーバから取得した時刻: 1900/1/1からの時間を1970/1/1からの基準に合わせます。
cuTimeStamp -= 2208988800L;

// 日付: 24hours x 60minutes x 60seconds
cuDays = cuTimeStamp / (24 * (60 * 60));
// 日付のあまり: 本日経過した秒数
cuTempSecs = cuTimeStamp % (24 * (60 * 60));

// 時間: 60m x 60s
cuHours = cuTempSecs / (60 * 60);
// 時間のあまり秒: 残りの'分'以下
cuTempSecs = cuTempSecs % (60 * 60);

// 何分かの計算
cuMinutes = cuTempSecs / 60;
// その残りが秒数
cuTempSecs = cuTempSecs % 60;

// 1970/1/1に算出した日数を加算し、時/分/秒を連結して現在日時とします。
dtTime = DateTime.Parse(string.Format("{0:yyyy/MM/dd} {1:00}:{2:00}:{3:00}", new DateTime(1970, 01, 01).AddDays((double)cuDays), (int)cuHours, (int)cuMinutes, (int)cuTempSecs));

// 日本時刻へ変更します。 GMT+9時間
newTime = dtTime = dtTime.AddHours(9);

string tm = dtTime.ToString("yyyy/MM/dd hh:mm:ss");
WriteLog("時刻: " + tm);

ret = true;
} else {
WriteLog("時刻 修正なし");
}
} catch (Exception e) {
string tm = e.Message + "\r\n";
tm += e.StackTrace;
WriteLog(tm);
}
return ret;
}
}


2. sampleクラス。
public class sample {
public void sample() {
NTPClient ntp = new NTPClient("time.windows.com");
Console.WriteLine("現在時刻: " + ntp.GetCurrentTime());
}
}

Shutdown (C#.Net)      ▲ページの先頭に戻る
Windowsをシャットダウンして、マシンの電源を落とすアプリケーションを作成します。
※参考URL

以下に、C#.Netによる実装方法を簡単に示します。

1. Shutdownクラスを作成します。
シャットダウンを行う前に、プロセスの特権が必要となります。
 OpenProcessToken()により、現在のプロセスのアクセストークンを開きます。
 LookupPrivilegeValue()により、シャットダウン特権の識別子を取得します。
 AdjustTokenPrivileges()により、シャットダウン特権を有効に設定します。
 ExitWindowsEx()により、シャットダウンを行います。
シャットダウンといっても、電源を落とすだけでなく、ログオフ、再起動を行うことが可能です。
そのためにはExitWindowsEx()にそれぞれ対応する(ExitWindowExFlags列挙子の)値を指定します。

2. 実行用クラスを作成します。



1. Shutdownクラス。
public class Shutdown {
/// <summary>
/// 情報取得用トークン
/// </summary>
private const uint TOKEN_QUERY = 0x8;

/// <summary>
/// 特権を変更できるトークン
/// </summary>
private const uint TOKEN_ADJUST_PRIVILEGES = 0x20;

/// <summary>
/// 特権を有効にする値
/// </summary>
private const uint SE_PRIVILEGE_ENABLED = 0x2;

/// <summary>
/// システムシャットダウン用の特権名
/// </summary>
private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";

/// <summary>
/// 現在のプロセス情報を取得します
/// </summary>
[DllImport("kernel32")]
private static extern IntPtr GetCurrentProcess();

/// <summary>
/// プロセスのアクセストークンを開きます
/// </summary>
[DllImport("advapi32")]
private static extern int OpenProcessToken(IntPtr processHandle, uint desiredAccess, ref IntPtr tokenHandle);

/// <summary>
/// 指定された特権名のLUID(ローカル一意識別子)を取得します
/// </summary>
[DllImport("advapi32", EntryPoint = "LookupPrivilegeValueA")]
private static extern int LookupPrivilegeValue(string systemName, string name, ref long luid);

/// <summary>
/// LUID(ローカル一意識別子)と属性を格納する構造体
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct LUID_AND_ATTRIBUTES {
public long Luid;
public uint Attributes;
}

/// <summary>
/// アクセストークン構造体
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct TOKEN_PRIVILEGES {
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privilege;
}

/// <summary>
/// システム終了の動作区分列挙子
/// </summary>
[Flags]
public enum ExitWindowExFlags : uint {
EWX_LOGOFF = 0,
EWX_SHUTDOWN = 1,
EWX_REBOOT = 2,
EWX_FORCE = 4,
EWX_POWEROFF = 8
}

/// <summary>
/// 指定したアクセストークン内の特権を有効または無効にします
/// </summary>
[DllImport("advapi32")]
private static extern int AdjustTokenPrivileges(IntPtr tokenHandle, bool disableAllPrivilege, ref TOKEN_PRIVILEGES newState, uint bufferLength, IntPtr previousState, IntPtr returnLength);

/// <summary>
/// システムをシャットダウンします
/// </summary>
[DllImport("user32")]
private static extern int ExitWindowsEx(ExitWindowExFlags flags, uint reserved);

/// <summary>
/// システムをシャットダウンしてマシンの電源を切ります
/// </summary>
public static void PowerOff() {
try {
// 現在のプロセスのアクセストークンを開きます。
IntPtr hToken = IntPtr.Zero;
int ret = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref hToken);

// シャットダウン特権の識別子を取得します。
long luid = 0;
ret = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref luid);

// シャットダウン特権を有効に設定します。
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privilege.Luid = luid;
tp.Privilege.Attributes = SE_PRIVILEGE_ENABLED;
ret = AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);

// Windowsを強制的にシャットダウンし、電源を切ります。
ret = ExitWindowsEx(ExitWindowExFlags.EWX_POWEROFF | ExitWindowExFlags.EWX_FORCE, 0);
} catch (Exception e) {
MessageBox.Show("ERROR: " + e.Message, "SHUTDOWN", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}

2. 実行用クラス。
static void Main(string[] args) {
Console.WriteLine("10秒後に強制的にシャットダウンします。");
System.Threading.Thread.Sleep(10 * 1000);
Shutdown.PowerOff();
}

Wake On LAN (Perl)      ▲ページの先頭に戻る
Wake On LAN(以降WOL)に対応している環境(マザーボード、NIC)のPCをリモートから起動します。

WOLの仕組み (詳細はWake on LAN mini HOWTOをご覧ください。以下はその一部の要約です。)

・WOLとは、特殊なパケットを送ることでリモートPCを起動する方法です。ただし、対象PCのマザーボードとネットワークカード(以降NIC)がWOLに対応している必要があります。

・PCの電源が落ちていても、NICには電源が供給されていて‘マジックシーケンス’を含むパケットが届くのを待っています。(電源コード/ACアダプタを接続している必要があります。)

・以下で紹介するWakeOnLANプログラムは、UDPを使用してパケットを送出します。
 次のようなUDPパケット(以降Wake-up フレーム)になります。

[ethernet header][IP header][UDP header][Magic sequence][CRCS]

・WakeOnLANプログラムの目的は、単純に上記のパケットをネットワークに流すだけです。
 対象PCからの戻りのデータは必要ありません(NICがそのパケットを待ち続けるだけで何も応答しません)。

【Wake-up フレーム】
Wake-upフレームはMACアドレスを含む特殊なデータパケットです。
このフレーム中には、6個のFFと、16回連続でMACアドレスを並べた‘マジックシーケンス’を含みます。

たとえば、対象PCのMACアドレスが 01:02:03:04:05:06 の場合、‘マジックシーケンス’は次のようになります。

FFFFFFFFFFFF010203040506010203040506010203040506010203040506
010203040506010203040506010203040506010203040506010203040506
010203040506010203040506010203040506010203040506010203040506
010203040506010203040506

※BIOS、NICの設定は個々のマニュアルを確認して、WOLを有効に設定してください。
※ノートPCでは、ACアダプタを接続している必要があります。(詳細はマニュアルで確認してください。)

以下に、http://gsd.di.uminho.pt/jpo/software/wakeonlan/で公開されているモジュールを使用してWOLを実現する方法を示します。

1. Perlを使用可能な環境にしてください。(ActivePerl)

2. WakeOnLANの最新モジュールwakeonlan-0.41.tar.gzをダウンロード、展開してください。
・READMEファイルには、インストール手順が書かれていますが、インストールをする必要はありません。
・「wakeonlan」というファイルがあるので、それをそのまま使用します。

3. 対象PCのMACアドレスを指定してwakeonlanスクリプトを実行します。
・MACアドレスを調べるには、Windowsの場合コマンドプロンプト上で“ipconfig /all”を実行します。


3. スクリプト実行
C:\WOL\script>  perl   wakeonlan   00:12:34:56:78:9A

DDE (C#.Net)      ▲ページの先頭に戻る
DDE…Dynamic Data Exchangeの略です。動的データ交換を意味し、Windows上で動作する複数のアプリケーション間でデータを交換(通信)する仕組みです。
※参考URL

◆ プログラムコードについては、只今準備中です。