Internet Programming

SNTPクライアントの作成(同期型)

[1997/11/28]

実際にUDPクライアントを作成する例として、SNTPクライアントを作成してみます。
SNTPは、NTPサーバから時刻を取得するプロトコルです。
SNTP(Simple Network Time Protocol)についての詳細は、RFC 1769を参照してください。

SNTPクライアントの流れは、
  1. ソケットの作成を行う。

  2. 受信ポートを指定する。

  3. NTPサーバにパケットを送信する。

  4. NTPサーバから時刻の情報を受信する。

  5. ソケットを破棄する。
という感じになります。
(上記はNTPサーバにリクエストを出して時刻情報を受信するパターンです)
ブロードキャストしている時刻情報を取得するには、「3. NTPサーバにパケットを送信する。」を外します。

以下のソースは、サンプル中の実際にNTPサーバから時刻情報を取得する部分です。

このサンプルでは、タイムアウトを行うのに select関数を使っています。
select関数は、ソケットの状態を調べる関数です。
複数のソケットを扱うときに、どのソケットに受信データがあるかなどを調べられます。


/* WinSockの初期化を行う処理 */
	/* WinSockの初期化を行う */
	wVersionRequested = MAKEWORD(1,1);			/* バージョン 1.1 を要求する */
	nErrorStatus = WSAStartup(wVersionRequested,&wsaData);
	if(atexit((void (*)(void))(WSACleanup))){		/* 終了時にWinSockのリソースを解放するようにしておく */
		fprintf(stderr,"Error: atexit(WSACleanup)失敗\n");
		return;
	}
	if(nErrorStatus != 0){
		fprintf(stderr,"Error: WinSockの初期化失敗\n");
		return;
	}

/* ソケットを作成する処理 */
	/* UDPモードでsocにソケットを作成します */
	soc = socket(PF_INET, SOCK_DGRAM, 0);
	if(soc == INVALID_SOCKET){
		fprintf(stderr,"Error: Socket作成失敗\n");
		return;
	}

/* 受信ポートを指定する処理 */
	/* ソケットのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
	sockname.sin_family	   = AF_INET;					/* インターネットの場合 */
	sockname.sin_addr.s_addr  = INADDR_ANY;				/* 自分のIPアドレスを使うようにする */
	sockname.sin_port	   = htons((unsigned short)local_port);		/* 受信するポート番号 */
	memset(sockname.sin_zero,(int)0,sizeof(sockname.sin_zero));
	if(bind(soc,(struct sockaddr *)&sockname,sizeof(sockname)) == SOCKET_ERROR){
		fprintf(stderr,"受信ポート番号の指定に失敗\n");
	}

/* サーバのIPアドレスを取得する処理 */
	/* svNameにドットで区切った10進数のIPアドレスが入っている場合、serveraddrに32bit整数のIPアドレスが返ります */
	serveraddr = inet_addr((char*)svName);
	if(serveraddr == -1){
		/* サーバ名(svName)からサーバのホスト情報を取得します */
		serverhostent = gethostbyname(svName);
		if(serverhostent == NULL) {
			fprintf(stderr,"Error: ホストアドレス取得失敗\n");
			/* ソケットを破棄する */
			closesocket(soc);
			return;
		}else{
			/* サーバのホスト情報からIPアドレスをserveraddrにコピーします */
			serveraddr = *((unsigned long *)((serverhostent->h_addr_list)[0]));
		}
	}

	/* サーバのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
	serversockaddr.sin_family	 = AF_INET;				/* インターネットの場合 */
	serversockaddr.sin_addr.s_addr  = serveraddr;				/* サーバのIPアドレス */
	serversockaddr.sin_port	 = htons((unsigned short)port);		/* ポート番号 */
	memset(serversockaddr.sin_zero,(int)0,sizeof(serversockaddr.sin_zero));

/* サーバにパケットを送信する処理 */
	/* NTPパケットをSNTP用に初期化する */
	NTP_Send.Control_Word = htonl(0x0B000000);
	NTP_Send.root_delay = 0;
	NTP_Send.root_dispersion = 0;
	NTP_Send.reference_identifier = 0;
	NTP_Send.reference_timestamp = 0;
	NTP_Send.originate_timestamp = 0;
	NTP_Send.receive_timestamp = 0;
	NTP_Send.transmit_timestamp_seconds = 0;
	NTP_Send.transmit_timestamp_fractions = 0;
	/* サーバを指定してNTPパケットを送信する */
	if(sendto(soc,(const char *)&NTP_Send, sizeof( NTP_Send ),0,(struct sockaddr *)&serversockaddr,sizeof(serversockaddr)) == SOCKET_ERROR){
		fprintf(stderr,"Error: サーバへの送信失敗\n");
		/* ソケットを破棄する */
		closesocket(soc);
		return;
	}

/* タイムアウトを行う処理 */
	/* select関数を使ってタイムアウトを設定する */
	waittime.tv_sec = TIMEOUT;	/* タイムアウト秒数を設定する */
	waittime.tv_usec = 0;
	FD_ZERO(&rdps);			/* 初期化する */
	FD_SET(soc,&rdps);		/* selectするソケットを追加する */

	selret = select(FD_SETSIZE,&rdps,(fd_set *)0,(fd_set *)0,&waittime);
	if(selret == SOCKET_ERROR){			/* エラーの場合 */
		fprintf(stderr,"Error: サーバからの受信失敗\n");
		/* ソケットを破棄する */
		closesocket(soc);
		return;
	}
	if(selret == 0){				/* タイムアウトの場合 */
		fprintf(stderr,"Error: タイムアウトしました\n");
		/* ソケットを破棄する */
		closesocket(soc);
		return;
	}
	if(FD_ISSET(soc,&rdps) == FALSE){		/* 受信はしたが、指定のソケットではない場合 */
		fprintf(stderr,"Error: サーバからの受信失敗\n");
		/* ソケットを破棄する */
		closesocket(soc);
		return;
	}

/* サーバから時刻情報を受信する処理 */
	/* サーバを指定して受信を行う */
	sockaddr_Size = sizeof(serversockaddr);
	if(recvfrom(soc, (char *)&NTP_Recv, sizeof(NTP_Recv), 0,(struct sockaddr *)&serversockaddr,&sockaddr_Size) == SOCKET_ERROR ){
		fprintf(stderr,"Error: サーバからの受信失敗\n");
		/* ソケットを破棄する */
		closesocket(soc);
		return;
	}


/* ソケットを破棄する処理 */
	/* ソケットを破棄する */
	closesocket(soc);



サンプルのダウンロード:

sntpc00.zip
- sntpc.c
- sntpc.exe
このプログラムはコンソールで動作します。

サンプルを実行する時に、
    sntpc NTPサーバ
と入力するとNTPサーバの時刻とローカルマシンの時刻を表示します。
ローカルマシンをNTPサーバの時刻と同期させるには、
    sntpc NTPサーバ -s
としてください。
NTPサーバについては、http://www.ntp.org/を参照してください。
※環境変数に TZ=JST-9の指定がないと9時間ずれた時刻になってしまいます。

sntpc.cをコンパイルする場合は、wsock32.lib をリンクしてください。

サンプルは、NTPサーバに時刻情報をリクエストしてから取得していますが、ブロードキャストしている時刻情報を取得する場合は、送信部分(sendto)とタイムアウト処理部分(select)を削るとブロードキャストしている時刻情報を取得できるはずです。



mail
メールアドレス
<nakka@nakka.com>


Internet Programming
Internet Programming