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 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 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(); } } }