←戻る

NTPを使ってみる

What's NTP?

そもそもNTPとはなんぞや。その理解

e-Wordsによると、コンピュータの内部時計を、ネットワークを介して正しく調整するプロトコル。 階層構造を持ち、最上位のサーバが GPS等を利用して正しい時刻を得、 下位のホストはそれを参照する事で時刻を合わせる。とある。要するに時計合わせ用プロトコルというなんとも地味なプロトコルだが、ちゃんとRFC868として定められているし、国内だけでも10を越えるNTPサーバが存在しているというれっきとしたプロトコルである。が、そもそもの制定の経緯が、One motivation arises from the fact that not all systems have a date/time clock, and all are subject to occasional human or machine error.(日本語訳:日時を保持する時計が無いシステムの存在や、時計があってもその時刻は人間や機械によって適当に合わせられているという事実が Time Protocol を定める動機になった。)とあるように、やっぱり地味路線致し方なしといった感じのプロトコルである。ちなみにサーバから得られる時刻は1900/01/01(Mon)00:00:00からサーバ呼び出し時までの秒数である。

How to use NTP?

どうやって使う?

一般にプロトコルというものはRFCの記述どおりにやれば一応使えるものである。RFC868にはTCP、UDPのどちらを使っても良し、と書いてある。以下にNTP使用の大まかな流れを示す。(S:はサーバの動作、U:はユーザ(?)の動作)

TCPUDP
S: 37番ポートへの接続要求をlistenする

U: 37番ポートへ接続する

S: 32bitのバイナリデータとして、時刻情報を送信する

U: 時刻情報を受信する

U: 接続を閉じる

S: 接続を閉じる
S: 37番ポートへの接続要求をlistenする

U: 37番ポートへ空のデータグラムを送信する

S: 空のデータグラムを受信する

S: 時刻情報を32bitのバイナリデータとしてダイアグラムに入れて送信する

U: 時刻情報の入ったデータグラムを受け取る

Coding 1

まずは簡単にコーディングの流れを

今回はWinsock Ver.2.0を使用し、TCP接続でNTPを取り扱う。と、いうことで、簡単なコーディングの流れは上記の表と照らし合わせて、以下のようになる。

  1. Winsockの初期化
  2. ソケットの初期化
  3. サーバに接続
  4. データを受信
  5. 接続を閉じる
  6. ソケットの後始末
  7. Winsockの後始末

Coding 2

もっと具体的に

具体的に、ということで余計な講釈はたれず、ソースを見ていただくことにしよう。その方が啓発的な理解にも繋がりますからな(´ー`y (重要な部分は色を変えて示しています)#ちなみに使用言語はC++(開発環境はVisualC++6.0)

FCNTP.h

#include <winsock2.h>
#include <exception>

class FCNTP_EXCP : public exception // 例外クラス。 たいした事はしてない
{
public:
	FCNTP_EXCP() throw () {m_strData = "Unknown Exception"; }
	FCNTP_EXCP(const char *strException) throw (){m_strData = strException; }

	const char *what() throw() { return m_strData; }

private:
	const char *m_strData;
};

class FCNTP
{
public:
	FCNTP() throw(FCNTP_EXCP); // コンストラクタ。 例外を投げる可能性があることに注意
	~FCNTP();

	int GetDataFromServer(unsigned long *pulTime, const char *strServerIP) throw(FCNTP_EXCP);
};
FCNTP.cpp

#include "FCNTP.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

FCNTP::FCNTP()
{
	WSADATA wsad;
	short int wVersion;
	int err;

	wVersion = MAKEWORD(2, 0);

	err = WSAStartup(wVersion, &wsad); // Winsockスタート
	if (err != 0) {
		throw FCNTP_EXCP("WSAStartup()失敗したYO(Д");
	}

	// 要求したバージョンが使用可能かどうか調べる
	if (HIWORD(wVersion) != HIWORD(wsad.wVersion) || LOWORD(wVersion) != LOWORD(wsad.wVersion)) {
		WSACleanup();
		throw FCNTP_EXCP("要求したバージョン使えねーじゃん(Д");
	}

}

FCNTP::~FCNTP()
{
	WSACleanup();
}

int FCNTP::GetDataFromServer(unsigned long *pulTime, const char *strServerIP)
{
	SOCKET s;
	unsigned long ulRVal;
	long int dwAddr;
	sockaddr_in si;
	int err;

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s == INVALID_SOCKET) {
		throw FCNTP_EXCP("ソケット使えねーじゃん(Д");
	}

	dwAddr = inet_addr(strServerIP);
	memset(&si, 0, sizeof(si));
	si.sin_family = AF_INET;
	si.sin_port = htons(37); // ポート番号をネットワークバイトオーダーに変換
	memmove(&si.sin_addr, &dwAddr, 4); 

	err = connect(s, reinterpret_cast<sockaddr *>(&si), sizeof(si));
	if (err == SOCKET_ERROR) {
		closesocket(s);
		throw FCNTP_EXCP("ソケット繋がんねーじゃん(Д");
	}

	ulRVal = 0L;
	err = recv(s, reinterpret_cast<char *>(&ulRVal), 4, 0); // 32bitのデータを期待
	if (err == SOCKET_ERROR) {
		closesocket(s);
		throw FCNTP_EXCP("なんか受信できねーぞ(Д");
	}
	*pulTime = ntohl(ulRVal); // 返り値をネットワークバイトオーダーから変換

	err = closesocket(s);
	if (err == SOCKET_ERROR) {
		throw FCNTP_EXCP("ぉぃぉぃ、ソケット閉じれんぞ(;Д");
	}

	return 0;
}

Winsockの初期化をコンストラクタ、Winsockの後始末をデストラクタ、そしてソケットの初期化〜ソケットの後始末をint FCNTP::GetDataFromServer(unsigned long *pulTime, const char *strServerIP)で行っています。非常に簡単なクラスで、通信用の関数に食われる時間は考慮してませんが、ちょっとしたテストに供するには問題ないと思います。ちなみに使用例は以下の通り。


#include "FCNTP.h"
#include <memory>
#include <iostream>

int main()
{
	unsigned long ulTime;
	std::auto_ptr<FCNTP> pNTP;
	try {
		pNTP = std::auto_ptr<FCNTP>(new FCNTP);
		pNTP->GetDataFromServer(&ulTime, "133.31.30.8"); // 東京理科大学のNTPサーバ
	} catch (FCNTP_EXCP e) {
		std::cout << e.what() << std::endl;
		exit(1);
	}
	
	std::cout << "現在、1900/01/01-00:00:00から数えて " << ulTime << " 秒後です。" << std::endl;
	
	return 0;
}

参考文献等


←戻る

Valid HTML 4.01!

Valid CSS!