SNTP

SNTPプロトコルによる、時刻合わせ処理の実装です。

Windows2000sp4/VC++6sp6/SDK

SNTP



// SNTP通信による時刻合わせを行なう
class CSntp
{
    // NTPパケットのフォーマット
    struct NTP_Packet {
        __int32    Control_Word;
        __int32    root_delay;
        __int32    root_dispersion;
        __int32    reference_identifier;
        __int64    reference_timestamp;
        __int64    originate_timestamp;
        __int64    receive_timestamp;
        __int32    transmit_timestamp_seconds;
        __int32    transmit_timestamp_fractions;
    };

    CString m_strError;        // 返値メッセージ
public:
    CString SNTP_use()
    {
        SNTP_impl();
        return m_strError;
    }

private:
    // ブロックモードの指定
    int SetSocketNonBlockMode(SOCKET s, BOOL bNonBlockMode)
    {
        u_long non_block = static_cast<u_long>( bNonBlockMode );
        return ::ioctlsocket(s, FIONBIO, &non_block);
    }

    bool SNTP_impl(/*const char *lpszDottedNotation, */bool bUpdate = true)
    {
        // 設定値取得
        CString strServer, strServer_Port;
        int nPort, nTimeout;

        // CNtpOption::GetServer(strServer, nPort, nTimeout, bNoDialog);
        strServer = _T("ntp.nict.jp");
        nPort = 123;
        nTimeout = 3000;

        strServer_Port.Format(_T("%s:%d"), strServer, nPort);

        // WinSock初期化
        WSADATA wsaData;
        if (::WSAStartup(MAKEWORD(1, 1), &wsaData)){
            m_strError = _T("WinSockが存在しません。\nwinsock.dll が存在しない可能性があります。");
            return false;
        }

        // アドレスの指定
        sockaddr_in addr;
        u_long dwIPAddr = ::inet_addr(strServer);
        if (INADDR_NONE != dwIPAddr) {
            addr.sin_addr.s_addr = dwIPAddr;
        }else{
            hostent* pHost = ::gethostbyname(strServer);
            if( pHost != NULL ){
                ::memcpy(&addr.sin_addr, pHost->h_addr, pHost->h_length);
            }else{
                m_strError = _T("宛先IPアドレスが正しくありません。\nまたは名前解決できませんでした。\n") + strServer_Port;
                ::WSACleanup();
                return false;
            }
        }

        // ソケットの作成
        SOCKET s = ::socket(PF_INET, SOCK_DGRAM, 0);
        if (INVALID_SOCKET == s){
            m_strError.Format(_T("ソケット作成失敗 Error = %d"), ::WSAGetLastError());
            ::WSACleanup();
            return false;
        }

        addr.sin_family = AF_INET;
        addr.sin_port = ::htons(static_cast<u_short>(nPort));
        if (SOCKET_ERROR == ::connect(s, reinterpret_cast<const sockaddr*>( &addr ), sizeof(addr))){
            m_strError.Format(_T("接続失敗 Error = %d"), ::WSAGetLastError());
            ::closesocket(s);
            ::WSACleanup();
            return false;
        }

        // ソケットをノンブロックモードにする
        SetSocketNonBlockMode(s, TRUE);

        // テスト開始
        NTP_Packet NTP_Request = {0};
        NTP_Request.Control_Word = ::htonl(0x0B000000);

        u_long dwStartTime = ::GetTickCount();
        if (::send(s, reinterpret_cast<const char*>( &NTP_Request ), sizeof(NTP_Request), 0) != sizeof(NTP_Request)){
            m_strError.Format(_T("送信失敗 Error = %d"), ::WSAGetLastError());
            ::closesocket(s);
            ::WSACleanup();
            return false;
        }

        time_t CurrentTime = 0, NewTime = 0;    // Time value in RFC868 format
        SYSTEMTIME TimeCell;    // Holds System specific time value
        NTP_Packet NTP_Reply;    // Holds time value from NTP server
        do{
            if (::recv(s, reinterpret_cast<char*>( &NTP_Reply ), sizeof(NTP_Reply), 0) > 0){
                // 現在時刻の取得
                CurrentTime = time(NULL);
                NewTime = NTP_Reply.transmit_timestamp_seconds;    // 1900/01/01からの時間を返す

                // 1970/01/01からの秒数に変換
                NewTime = ::ntohl(NewTime) - 2208988800L;

                // ローカル時刻の計算
                tm* lpNewLocalTime = ::localtime(&NewTime);

                // struct tm を SYSTEMTIME に変換
                TimeCell.wYear   = lpNewLocalTime->tm_year + 1900;
                TimeCell.wMonth  = lpNewLocalTime->tm_mon  + 1;
                TimeCell.wDay    = lpNewLocalTime->tm_mday;
                TimeCell.wHour   = lpNewLocalTime->tm_hour;
                TimeCell.wMinute = lpNewLocalTime->tm_min;
                TimeCell.wSecond = lpNewLocalTime->tm_sec;

                // ミリ秒の計算
                float SplitSeconds;    // Split seconds returned from NTP
                SplitSeconds = static_cast<float>( ::ntohl(NTP_Reply.transmit_timestamp_fractions) );
                SplitSeconds = static_cast<float>( 0.000000000200 * SplitSeconds );
                SplitSeconds = static_cast<float>( 1000.0 * SplitSeconds );
                TimeCell.wMilliseconds    = static_cast<unsigned short>( SplitSeconds );
                break;
            }
        }while((::GetTickCount() - dwStartTime) <= static_cast<DWORD>( nTimeout ));

        // 結果表示
        bool bIsSucceeded = false;
        if (CurrentTime){
            if (bUpdate) {
                if (::SetLocalTime(&TimeCell)){
                    bIsSucceeded = true;
                } else {
                    RTL_ErrMsg();
                    m_strError.Format(_T("時刻設定に失敗しました。ERRCODE = %ld"), GetLastError());
                }
            }

            // make message
            tm* lpCurTime = ::localtime(&CurrentTime);
            ::sprintf(m_strError.GetBuffer(MAX_PATH),
                _T("ローカル時刻:\t%02d:%02d:%02d\nNTPサーバ時刻:\t%02d:%02d:%02d.%d\n結果:\t%+6.f秒の誤差\n"),
                lpCurTime->tm_hour, lpCurTime->tm_min, lpCurTime->tm_sec,
                TimeCell.wHour, TimeCell.wMinute, TimeCell.wSecond, TimeCell.wMilliseconds,
                ::difftime(CurrentTime, NewTime));
            m_strError.ReleaseBuffer();

            if (bIsSucceeded){
                m_strError += _T("\nNTPサーバの時刻に同期しました。");
            }
        }else{
            m_strError = _T("NTPサーバからの応答がありません。\n") + strServer_Port;
        }

        // 試験終了
        ::closesocket(s);
        ::WSACleanup();
        return bIsSucceeded;
    }
};




[HOME]-[RTL]
Copyright© 2005-2006 RAPT.