Internet Programming
SNTPクライアントの作成(非同期型)
[1997/12/01]
実際に非同期APIを使用したUDPクライアントを作成する例として、SNTPクライアントを作成してみます。
SNTPは、123番ポートを使用してNTPサーバから時刻を取得するプロトコルです。
SNTP(Simple Network Time Protocol)についての詳細は、RFC 1769を参照してください。
SNTPクライアントの流れは、
- ソケットの作成を行う。
- 受信ポートを指定する。
- NTPサーバにパケットを送信する。
- NTPサーバから時刻の情報を受信する。
- ソケットを破棄する。
という感じになります。
(上記はNTPサーバにリクエストを出して時刻情報を受信するパターンです)
ブロードキャストしている時刻情報を取得するには、「3. NTPサーバにパケットを送信する。」を外します。
以下のソースは、サンプル中の実際にNTPサーバから時刻情報を取得する部分です。
このサンプルでは、タイムアウトを行うのにタイマーを使っています。
#define WSOCK_GETHOST WM_USER + 1 /* ホスト情報のイベントを通知するメッセージ */
#define WSOCK_SELECT WM_USER + 2 /* ソケットイベントを通知するメッセージ */
/*---------------------------------------------------
ソケットイベントの通知を設定する関数
---------------------------------------------------*/
BOOL SocketSelect(int sSoc,HWND hWnd)
{
/* 受信のソケットイベントを通知させるように設定する */
/* ソケットイベントは、ウィンドウにWSOCK_SELECTメッセージで通知されるように設定する */
if(WSAAsyncSelect(sSoc, hWnd, WSOCK_SELECT, FD_READ) == SOCKET_ERROR){
return FALSE;
}
return TRUE;
}
/*---------------------------------------------------
サーバにアドレスにIPアドレスとポート番号を設定する
---------------------------------------------------*/
void SetSockaddr(unsigned long cIPaddr,int cPort)
{
/* サーバのアドレスの構造体にサーバのIPアドレスとポート番号を設定します */
serversockaddr.sin_family = AF_INET;
serversockaddr.sin_addr.s_addr = cIPaddr; /* IPアドレス */
serversockaddr.sin_port = htons((unsigned short)cPort); /* ポート番号 */
memset(serversockaddr.sin_zero,(int)0,sizeof(serversockaddr.sin_zero));
}
/*---------------------------------------------------
NTPパケットを送信する
---------------------------------------------------*/
BOOL SendSNTPPacket(int sSoc)
{
struct NTP_Packet NTP_Send; /* 送信するNTPパケット */
/* 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(sSoc,(const char *)&NTP_Send, sizeof( NTP_Send ),0,(struct sockaddr *)&serversockaddr,sizeof(serversockaddr)) == SOCKET_ERROR){
return FALSE;
}
return TRUE;
}
/*---------------------------------------------------
ソケットを破棄する関数
---------------------------------------------------*/
void SocketClose(HWND hWnd)
{
/* タイムアウト用のタイマーを破棄する */
KillTimer(hWnd,TIMER_ID);
/* WSAAsyncGetHostByName関数の非同期なタスクがある場合はキャンセルする */
if(hGetHost != NULL){
WSACancelAsyncRequest(hGetHost);
hGetHost = NULL;
}
/* ソケットイベントの通知を取り消す */
WSAAsyncSelect(soc, hWnd, 0,0);
/* ソケットを破棄する */
closesocket(soc);
soc = -1;
if(sync_flag == FALSE){
SetWindowText(hBTNGET,"GET");
}else{
SetWindowText(hBTNSYNC,"SYNC");
}
}
/*---------------------------------------------------
ソケットのエラー処理関数
---------------------------------------------------*/
void SocketError(HWND hWnd,char *msgbuf)
{
/* ソケットを破棄する */
SocketClose(hWnd);
/* エラーメッセージ */
MessageBox(hWnd,msgbuf,"Error",MB_OK | MB_ICONERROR);
}
/*---------------------------------------------------
SNTPセッションの開始
---------------------------------------------------*/
void SNTPStart(HWND hWnd,char *buf)
{
unsigned long serveraddr; /* サーバのIPアドレス */
/* 入力文字からサーバ名とパスを取得する */
Port = GetServerPort(buf,SvName);
if(Port == -1){
MessageBox(hWnd, "サーバ名が入力されていません。", "Error", MB_OK | MB_ICONERROR);
if(sync_flag == FALSE){
SetWindowText(hBTNGET,"GET");
}else{
SetWindowText(hBTNSYNC,"SYNC");
}
return;
}
/* socにソケットを作成します */
soc = socket(PF_INET, SOCK_DGRAM, 0);
if(soc == INVALID_SOCKET){
MessageBox(hWnd, "Socketの作成に失敗しました。", "Error", MB_OK | MB_ICONERROR);
soc = -1;
if(sync_flag == FALSE){
SetWindowText(hBTNGET,"GET");
}else{
SetWindowText(hBTNSYNC,"SYNC");
}
return;
}
/* svNameにドットで区切った10進数のIPアドレスが入っている場合、serveraddrに32bit整数のIPアドレスが返ります */
serveraddr = inet_addr((char*)SvName);
if(serveraddr == -1){
/* サーバ名からサーバのホスト情報を取得する */
/* ホスト情報が取得できると、ウィンドウにWSOCK_GETHOSTが通知されるようにする */
hGetHost = WSAAsyncGetHostByName(hWnd,WSOCK_GETHOST,SvName,gHostEnt, MAXGETHOSTSTRUCT);
if(hGetHost == 0){
SocketError(hWnd,"サーバのIPアドレスの取得に失敗しました。");
return;
}
return;
}
/* 接続、受信、切断のイベントをウィンドウメッセージで通知されるようにする */
if(SocketSelect(soc, hWnd) == FALSE){
SocketError(hWnd,"Socketイベントを通知させる設定に失敗しました。");
return;
}
SetSockaddr(serveraddr,Port);
if(SendSNTPPacket(soc) == FALSE){
SocketError(hWnd,"送信に失敗しました。");
return;
}
/* タイムアウト用のタイマーをセットする */
SetTimer(hWnd, TIMER_ID, 3000, NULL);
}
/*---------------------------------------------------
IPアドレスを取得してデータを送信する
---------------------------------------------------*/
void GetHostToIPaddr(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
struct hostent FAR *HostEntry; /* ホスト情報 */
unsigned long serveraddr; /* サーバのIPアドレス */
/* エラーの判定 */
if(WSAGETASYNCERROR(lParam) != 0){
SocketError(hWnd,"サーバのIPアドレスの取得に失敗しました。");
return;
}
/* WSAAsyncGetHostByNameの戻り値である非同期なタスクのハンドルか判定*/
if(hGetHost != (HANDLE)wParam){
return;
}
HostEntry = (struct hostent FAR *)gHostEnt;
serveraddr = *((unsigned long *)((HostEntry->h_addr_list)[0])); /* IPアドレスを取得する */
hGetHost = NULL;
/* ソケットイベントをウィンドウメッセージで通知されるようにする */
if(SocketSelect(soc, hWnd) == FALSE){
SocketError(hWnd,"Socketイベントを通知させる設定に失敗しました。");
return;
}
SetSockaddr(serveraddr,Port);
if(SendSNTPPacket(soc) == FALSE){
SocketError(hWnd,"送信に失敗しました。");
return;
}
/* タイムアウト用のタイマーをセットする */
SetTimer(hWnd, TIMER_ID, 3000, NULL);
}
/*---------------------------------------------------
SNTPのデータを受信する
---------------------------------------------------*/
void SNTPRecvData(HWND hWnd)
{
struct NTP_Packet NTP_Recv; /* 受信するNTPパケット */
int sockaddr_Size; /* サーバのアドレスのサイズ */
char buf[BUFSIZE];
time_t Cur_time; /* ローカルマシンの時刻 */
time_t ntp_time; /* NTPサーバから取得した時刻 */
struct tm *lpNewLocalTime; /* 現地時刻に変換したNTPサーバの時刻 */
SYSTEMTIME Timecall; /* ローカルマシンに設定する時刻 */
float Splitseconds;
/* サーバを指定して受信を行う */
sockaddr_Size = sizeof(serversockaddr);
if(recvfrom(soc, (char *)&NTP_Recv, sizeof(NTP_Recv), 0,(struct sockaddr *)&serversockaddr,&sockaddr_Size) == SOCKET_ERROR ){
SocketError(hWnd,"受信に失敗しました。");
return;
}
/* ソケットを破棄する */
SocketClose(hWnd);
/* ローカルマシンの時刻を取得する */
Cur_time = time(NULL);
/* NTPサーバから取得した時刻を現地時間に変換する */
ntp_time = ntohl(NTP_Recv.transmit_timestamp_seconds) - 2208988800; /* 1970/01/01 からの秒数に変換 */
lpNewLocalTime = localtime((unsigned int *)&ntp_time);
if(lpNewLocalTime == NULL){
MessageBox(hWnd,"時刻の取得に失敗しました。","Error",MB_OK | MB_ICONERROR);
return;
}
/* 同期フラグが立っている場合は、時刻の同期を行う */
if(sync_flag == TRUE){
/* ローカル時刻の計算 */
Timecall.wYear = lpNewLocalTime->tm_year + 1900;
Timecall.wMonth = lpNewLocalTime->tm_mon + 1;
Timecall.wDay = lpNewLocalTime->tm_mday;
Timecall.wHour = lpNewLocalTime->tm_hour;
Timecall.wMinute = lpNewLocalTime->tm_min;
Timecall.wSecond = lpNewLocalTime->tm_sec;
Splitseconds = (float)ntohl(NTP_Recv.transmit_timestamp_fractions);
Splitseconds = (float)0.000000000200 * Splitseconds;
Splitseconds = (float)1000.0 * Splitseconds;
Timecall.wMilliseconds = (unsigned short)Splitseconds;
/* ローカル時刻をNTPサーバから取得した時刻に設定する */
if(SetLocalTime(&Timecall) == FALSE){
MessageBox(hWnd,"時刻の設定に失敗しました。","Error",MB_OK | MB_ICONERROR);
}else{
MessageBox(hWnd,"NTPサーバの時刻に同期しました。","info",MB_OK | MB_ICONINFORMATION);
}
}
/* ローカル時刻を表示する */
wsprintf(buf,"%s",ctime((unsigned int *)&Cur_time));
buf[strlen(buf) - 1] = '\0';
SendMessage(LocalEdit,WM_SETTEXT,0,(LPARAM) ((LPSTR)buf));
/* NTPサーバから取得した時刻を表示する */
wsprintf(buf,"%s",ctime((unsigned int *)&ntp_time));
buf[strlen(buf) - 1] = '\0';
SendMessage(NTPEdit,WM_SETTEXT,0,(LPARAM) ((LPSTR)buf));
}
/*---------------------------------------------------
メインウィンドウのプロシージャ
---------------------------------------------------*/
LONG APIENTRY MainProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
char buf[BUFSIZE];
switch (uMsg)
{
case WM_CREATE: /* ウィンドウ作成時 */
/* ウィンドウ内のコントロールを作成する */
CreateWindow("STATIC",(LPSTR)"Server:",WS_CHILD | WS_VISIBLE,5,10,50,25,hWnd,(HMENU)NULL,g_hinst,NULL);
CreateWindow("STATIC",(LPSTR)"Local:",WS_CHILD | WS_VISIBLE,5,50,50,25,hWnd,(HMENU)NULL,g_hinst,NULL);
CreateWindow("STATIC",(LPSTR)"NTP:",WS_CHILD | WS_VISIBLE,5,80,50,25,hWnd,(HMENU)NULL,g_hinst,NULL);
ServerEdit = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",(LPSTR)NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,60,10,300,25,hWnd,(HMENU)ID_EDIT_SERVER,g_hinst,NULL);
LocalEdit = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",(LPSTR)NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY,60,50,420,25,hWnd,(HMENU)ID_EDIT_CUR,g_hinst,NULL);
NTPEdit = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",(LPSTR)NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY,60,80,420,25,hWnd,(HMENU)ID_EDIT_NTP,g_hinst,NULL);
hBTNGET = CreateWindow("BUTTON",(LPSTR)"GET",WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,370,10,50,25,hWnd,(HMENU)ID_GET,g_hinst,NULL);
hBTNSYNC = CreateWindow("BUTTON",(LPSTR)"SYNC",WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,430,10,50,25,hWnd,(HMENU)ID_SYNC,g_hinst,NULL);
SetFocus(ServerEdit);
/* 初期化 */
soc = -1;
hGetHost = NULL;
break;
case WSOCK_GETHOST: /* サーバのホスト情報関数のイベント */
/* ホスト情報からIPアドレスを取得して接続を開始する */
GetHostToIPaddr(hWnd,wParam,lParam);
break;
case WSOCK_SELECT: /* ソケットイベントのメッセージの場合(WSAAsyncSelect関数にて登録) */
/* エラーの判定 */
if(soc != -1 && WSAGETSELECTERROR(lParam) != 0){
SocketError(hWnd,"Socketイベントの通知でエラーが発生しました。");
break;
}
/* 処理すべきソケットか判定 */
if(soc != (int)wParam){
break;
}
/* 受信のイベントの場合は処理を行う */
if(WSAGETSELECTEVENT(lParam) == FD_READ){
/* データを受信する */
SNTPRecvData(hWnd);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam)){
case ID_GET: /* 取得するボタンが押されたときの処理 */
if(soc != -1){ /* 既に通信中の場合はキャンセルする */
/* ソケットを破棄する */
SocketClose(hWnd);
}else{ /* 通信を開始する */
/* サーバ名を取得する */
SendMessage(ServerEdit,WM_GETTEXT,BUFSIZE - 1,(LPARAM)buf);
SetWindowText(hBTNGET,"Cancel");
/* 同期フラグを偽に設定する */
sync_flag = FALSE;
/* SNTTPセッションを開始する */
SNTPStart(hWnd,buf);
}
break;
case ID_SYNC: /* 同期するボタンが押されたときの処理 */
if(soc != -1){ /* 既に通信中の場合はキャンセルする */
/* ソケットを破棄する */
SocketClose(hWnd);
}else{ /* 通信を開始する */
/* サーバ名を取得する */
SendMessage(ServerEdit,WM_GETTEXT,BUFSIZE - 1,(LPARAM)buf);
SetWindowText(hBTNSYNC,"Cancel");
/* 同期フラグを真に設定する */
sync_flag = TRUE;
/* SNTPセッションを開始する */
SNTPStart(hWnd,buf);
}
break;
}
break;
case WM_TIMER: /* タイムアウト時の処理 */
if(wParam == TIMER_ID){
/* タイムアウトのメッセージを表示する */
SocketError(hWnd,"タイムアウトしました。");
break;
}
break;
:
:
サンプルのダウンロード:
sntpcex00.zip
- sntpcex.c
- sntpcex.rc
- resource.h
Res
- icon1.ico
- sntpcex.exe
このプログラムはGUIベースで動作します。
NTPサーバについては、http://www.ntp.org/を参照してください。
※環境変数に TZ=JST-9の指定がないと9時間ずれた時刻になってしまいます。
sntpcex.cをコンパイルする場合は、wsock32.lib をリンクしてください。
サンプルは、NTPサーバに時刻情報をリクエストしてから取得していますが、ブロードキャストしている時刻情報を取得する場合は、送信部分(sendto)とタイムアウト処理部分(タイマー)を削るとブロードキャストしている時刻情報を取得できるはずです。

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

Internet Programming