// ---------------------------------------------------------------------------- // // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH // // // This file includes various PhotonPing implementations for different APIs, // platforms and protocols. // The RegionPinger class is the instance which selects the Ping implementation // to use. // // developer@exitgames.com // ---------------------------------------------------------------------------- namespace Photon.Realtime { using System; using System.Collections; using System.Threading; #if NETFX_CORE using System.Diagnostics; using Windows.Foundation; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; #endif #if !NO_SOCKET && !NETFX_CORE using System.Collections.Generic; using System.Diagnostics; using System.Net.Sockets; #endif #if UNITY_WEBGL // import WWW class using UnityEngine; #endif /// /// Abstract implementation of PhotonPing, ase for pinging servers to find the "Best Region". /// public abstract class PhotonPing : IDisposable { public string DebugString = ""; public bool Successful; protected internal bool GotResult; protected internal int PingLength = 13; protected internal byte[] PingBytes = new byte[] { 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x00 }; protected internal byte PingId; private static readonly System.Random RandomIdProvider = new System.Random(); public virtual bool StartPing(string ip) { throw new NotImplementedException(); } public virtual bool Done() { throw new NotImplementedException(); } public virtual void Dispose() { throw new NotImplementedException(); } protected internal void Init() { this.GotResult = false; this.Successful = false; this.PingId = (byte)(RandomIdProvider.Next(255)); } } #if !NETFX_CORE && !NO_SOCKET /// Uses C# Socket class from System.Net.Sockets (as Unity usually does). /// Incompatible with Windows 8 Store/Phone API. public class PingMono : PhotonPing { private Socket sock; /// /// Sends a "Photon Ping" to a server. /// /// Address in IPv4 or IPv6 format. An address containing a '.' will be interpreted as IPv4. /// True if the Photon Ping could be sent. public override bool StartPing(string ip) { this.Init(); try { if (this.sock == null) { if (ip.Contains(".")) { this.sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); } else { this.sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); } this.sock.ReceiveTimeout = 5000; this.sock.Connect(ip, 5055); } this.PingBytes[this.PingBytes.Length - 1] = this.PingId; this.sock.Send(this.PingBytes); this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId+1); // this buffer is re-used for the result/receive. invalidate the result now. } catch (Exception e) { this.sock = null; Console.WriteLine(e); } return false; } public override bool Done() { if (this.GotResult || this.sock == null) { return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed } int read = 0; try { if (!this.sock.Poll(0, SelectMode.SelectRead)) { return false; } read = this.sock.Receive(this.PingBytes, SocketFlags.None); } catch (Exception ex) { if (this.sock != null) { this.sock.Close(); this.sock = null; } this.DebugString += " Exception of socket! " + ex.GetType() + " "; return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed } bool replyMatch = this.PingBytes[this.PingBytes.Length - 1] == this.PingId && read == this.PingLength; if (!replyMatch) { this.DebugString += " ReplyMatch is false! "; } this.Successful = replyMatch; this.GotResult = true; return true; } public override void Dispose() { try { this.sock.Close(); } catch { } this.sock = null; } } #endif #if NETFX_CORE /// Windows store API implementation of PhotonPing, based on DatagramSocket for UDP. public class PingWindowsStore : PhotonPing { private DatagramSocket sock; private readonly object syncer = new object(); public override bool StartPing(string host) { base.Init(); EndpointPair endPoint = new EndpointPair(null, string.Empty, new HostName(host), "5055"); this.sock = new DatagramSocket(); this.sock.MessageReceived += OnMessageReceived; var result = this.sock.ConnectAsync(endPoint); result.Completed = this.OnConnected; this.DebugString += " End StartPing"; return true; } public override bool Done() { return this.GotResult; } public override void Dispose() { this.sock = null; } private void OnConnected(IAsyncAction asyncinfo, AsyncStatus asyncstatus) { if (asyncinfo.AsTask().IsCompleted) { PingBytes[PingBytes.Length - 1] = PingId; DataWriter writer; writer = new DataWriter(sock.OutputStream); writer.WriteBytes(PingBytes); var res = writer.StoreAsync(); res.AsTask().Wait(100); writer.DetachStream(); writer.Dispose(); PingBytes[PingBytes.Length - 1] = (byte)(PingId - 1); } else { // TODO: handle error } } private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args) { lock (syncer) { DataReader reader = null; try { reader = args.GetDataReader(); uint receivedByteCount = reader.UnconsumedBufferLength; if (receivedByteCount > 0) { var resultBytes = new byte[receivedByteCount]; reader.ReadBytes(resultBytes); //TODO: check result bytes! this.Successful = receivedByteCount == PingLength && resultBytes[resultBytes.Length - 1] == PingId; this.GotResult = true; } } catch { // TODO: handle error } } } } #endif #if NATIVE_SOCKETS /// Abstract base class to provide proper resource management for the below native ping implementations public abstract class PingNative : PhotonPing { // Native socket states - according to EnetConnect.h state definitions protected enum NativeSocketState : byte { Disconnected = 0, Connecting = 1, Connected = 2, ConnectionError = 3, SendError = 4, ReceiveError = 5, Disconnecting = 6 } protected IntPtr pConnectionHandler = IntPtr.Zero; ~PingNative() { Dispose(); } } /// Uses dynamic linked native Photon socket library via DllImport("PhotonSocketPlugin") attribute (as done by Unity Android and Unity PS3). public class PingNativeDynamic : PingNative { public override bool StartPing(string ip) { lock (SocketUdpNativeDynamic.syncer) { base.Init(); if(pConnectionHandler == IntPtr.Zero) { pConnectionHandler = SocketUdpNativeDynamic.egconnect(ip); SocketUdpNativeDynamic.egservice(pConnectionHandler); byte state = SocketUdpNativeDynamic.eggetState(pConnectionHandler); while (state == (byte) NativeSocketState.Connecting) { SocketUdpNativeDynamic.egservice(pConnectionHandler); state = SocketUdpNativeDynamic.eggetState(pConnectionHandler); } } PingBytes[PingBytes.Length - 1] = PingId; SocketUdpNativeDynamic.egsend(pConnectionHandler, PingBytes, PingBytes.Length); SocketUdpNativeDynamic.egservice(pConnectionHandler); PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1); return true; } } public override bool Done() { lock (SocketUdpNativeDynamic.syncer) { if (this.GotResult || pConnectionHandler == IntPtr.Zero) { return true; } int available = SocketUdpNativeDynamic.egservice(pConnectionHandler); if (available < PingLength) { return false; } int pingBytesLength = PingBytes.Length; int bytesInRemainginDatagrams = SocketUdpNativeDynamic.egread(pConnectionHandler, PingBytes, ref pingBytesLength); this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId); //Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId); this.GotResult = true; return true; } } public override void Dispose() { lock (SocketUdpNativeDynamic.syncer) { if (this.pConnectionHandler != IntPtr.Zero) SocketUdpNativeDynamic.egdisconnect(this.pConnectionHandler); this.pConnectionHandler = IntPtr.Zero; } GC.SuppressFinalize(this); } } #if NATIVE_SOCKETS && NATIVE_SOCKETS_STATIC /// Uses static linked native Photon socket library via DllImport("__Internal") attribute (as done by Unity iOS and Unity Switch). public class PingNativeStatic : PingNative { public override bool StartPing(string ip) { base.Init(); lock (SocketUdpNativeStatic.syncer) { if(pConnectionHandler == IntPtr.Zero) { pConnectionHandler = SocketUdpNativeStatic.egconnect(ip); SocketUdpNativeStatic.egservice(pConnectionHandler); byte state = SocketUdpNativeStatic.eggetState(pConnectionHandler); while (state == (byte) NativeSocketState.Connecting) { SocketUdpNativeStatic.egservice(pConnectionHandler); state = SocketUdpNativeStatic.eggetState(pConnectionHandler); Thread.Sleep(0); // suspending execution for a moment is critical on Switch for the OS to update the socket } } PingBytes[PingBytes.Length - 1] = PingId; SocketUdpNativeStatic.egsend(pConnectionHandler, PingBytes, PingBytes.Length); SocketUdpNativeStatic.egservice(pConnectionHandler); PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1); return true; } } public override bool Done() { lock (SocketUdpNativeStatic.syncer) { if (this.GotResult || pConnectionHandler == IntPtr.Zero) { return true; } int available = SocketUdpNativeStatic.egservice(pConnectionHandler); if (available < PingLength) { return false; } int pingBytesLength = PingBytes.Length; int bytesInRemainginDatagrams = SocketUdpNativeStatic.egread(pConnectionHandler, PingBytes, ref pingBytesLength); this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId); //Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId); this.GotResult = true; return true; } } public override void Dispose() { lock (SocketUdpNativeStatic.syncer) { if (pConnectionHandler != IntPtr.Zero) SocketUdpNativeStatic.egdisconnect(pConnectionHandler); pConnectionHandler = IntPtr.Zero; } GC.SuppressFinalize(this); } } #endif #endif #if UNITY_WEBGL public class PingHttp : PhotonPing { private WWW webRequest; public override bool StartPing(string address) { base.Init(); address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000); this.webRequest = new WWW(address); return true; } public override bool Done() { if (this.webRequest.isDone) { Successful = true; return true; } return false; } public override void Dispose() { this.webRequest.Dispose(); } } #endif }