popcorn/Scripts/ApiRequest/NtpRequest.cs

127 lines
3.9 KiB
C#
Raw Normal View History

using UnityEngine;
using UnityEngine.Events;
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
// NTP同期時刻を返却するクラス
public class NtpRequest {
private DateTime ntpDateTime;
private Thread thread;
private Socket socket;
private volatile bool threadRunning = false;
private int timeout = 3000; // ms
private int retryCount = 0;
private string[] NTP_SERVER = {
"time.google.com",
"cn.pool.ntp.org",
};
void OnApplicationQuit() {
CloseRequest ();
}
public Coroutine GetNetworkTime(MonoBehaviour behaviour, Action<DateTime> succeededCallback, Action failedCallback, int timeout = 3000)
{
if (threadRunning) return null;
// リクエスト実行
retryCount = 0;
this.timeout = timeout;
ntpDateTime = DateTime.MinValue;
_threadStart();
return behaviour.StartCoroutine(WaitForRequest(succeededCallback, failedCallback));
}
private void _threadStart()
{
threadRunning = true;
thread = new Thread(new ThreadStart(NetworkRequest));
thread.Start();
}
private IEnumerator WaitForRequest(Action<DateTime> succeededCallback, Action failedCallback)
{
int startTickCount = Environment.TickCount;
bool isTimeouted = false;
while ( threadRunning ) {
if (ntpDateTime > DateTime.MinValue){
break;
}
// タイムアウト
else if (Environment.TickCount - startTickCount > timeout) {
isTimeouted = true;
break;
}
yield return 0;
}
CloseRequest ();
threadRunning = false;
if(!isTimeouted){
if(succeededCallback != null)
succeededCallback (ntpDateTime);
// リトライ
}else if(retryCount < NTP_SERVER.Length-1){
retryCount++;
isTimeouted = false;
_threadStart();
yield return WaitForRequest(succeededCallback, failedCallback);
}else{
failedCallback();
}
}
private void NetworkRequest()
{
try{
string ntpServer = NTP_SERVER[retryCount];
var ntpData = new byte[48];
//Setting the Leap Indicator, Version Number and Mode values
ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
var addresses = Dns.GetHostEntry(ntpServer).AddressList;
var ipEndPoint = new IPEndPoint(addresses[0], 123);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// ソケット通信開始
socket.Connect(ipEndPoint);
socket.Send(ntpData);
socket.Receive(ntpData);
socket.Close();
ulong intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | (ulong)ntpData[43];
ulong fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | (ulong)ntpData[47];
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
ntpDateTime = networkDateTime;
}catch(SocketException){
// TODO 例外フィルタでSocketException: Access deniedのみ取りたいが、C#のバージョンが足りない
// https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/try-catch
}catch(ThreadAbortException){
}
}
private void CloseRequest(){
if ( thread != null ) {
thread.Abort();
}
if ( socket != null ) {
socket.Close();
}
}
}