123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- // ----------------------------------------------------------------------------
- // <copyright file="PhotonPing.cs" company="Exit Games GmbH">
- // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
- // </copyright>
- // <summary>
- // 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.
- // </summary>
- // <author>developer@exitgames.com</author>
- // ----------------------------------------------------------------------------
- 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 UnityWebRequest
- using UnityEngine.Networking;
- #endif
- /// <summary>
- /// Abstract implementation of PhotonPing, ase for pinging servers to find the "Best Region".
- /// </summary>
- public abstract class PhotonPing : IDisposable
- {
- /// <summary>Caches the last exception/error message, if any.</summary>
- public string DebugString = "";
- /// <summary>True of the ping was successful.</summary>
- public bool Successful;
- /// <summary>True if there was any result.</summary>
- protected internal bool GotResult;
- /// <summary>Length of a ping.</summary>
- protected internal int PingLength = 13;
- /// <summary>Bytes to send in a (Photon UDP) ping.</summary>
- protected internal byte[] PingBytes = new byte[] { 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x00 };
- /// <summary>Randomized number to identify a ping.</summary>
- protected internal byte PingId;
- private static readonly System.Random RandomIdProvider = new System.Random();
- /// <summary>Begins sending a ping.</summary>
- public virtual bool StartPing(string ip)
- {
- throw new NotImplementedException();
- }
- /// <summary>Check if done.</summary>
- public virtual bool Done()
- {
- throw new NotImplementedException();
- }
- /// <summary>Dispose of this ping.</summary>
- public virtual void Dispose()
- {
- throw new NotImplementedException();
- }
- /// <summary>Initialize this ping (GotResult, Successful, PingId).</summary>
- protected internal void Init()
- {
- this.GotResult = false;
- this.Successful = false;
- this.PingId = (byte)(RandomIdProvider.Next(255));
- }
- }
- #if !NETFX_CORE && !NO_SOCKET
- /// <summary>Uses C# Socket class from System.Net.Sockets (as Unity usually does).</summary>
- /// <remarks>Incompatible with Windows 8 Store/Phone API.</remarks>
- public class PingMono : PhotonPing
- {
- private Socket sock;
- /// <summary>
- /// Sends a "Photon Ping" to a server.
- /// </summary>
- /// <param name="ip">Address in IPv4 or IPv6 format. An address containing a '.' will be interpreted as IPv4.</param>
- /// <returns>True if the Photon Ping could be sent.</returns>
- 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;
- int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
- this.sock.Connect(ip, port);
- }
- 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;
- System.Diagnostics.Debug.WriteLine(e.ToString());
- // bubble up
- throw;
- }
- return false;
- }
- /// <summary>Check if done.</summary>
- 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;
- }
- /// <summary>Dispose of this ping.</summary>
- public override void Dispose()
- {
- if (this.sock == null) { return; }
- try
- {
- this.sock.Close();
- }
- catch
- {
- }
- this.sock = null;
- }
- }
- #endif
- #if NETFX_CORE
- /// <summary>Windows store API implementation of PhotonPing, based on DatagramSocket for UDP.</summary>
- public class PingWindowsStore : PhotonPing
- {
- private DatagramSocket sock;
- private readonly object syncer = new object();
- public override bool StartPing(string host)
- {
- lock (this.syncer)
- {
- this.Init();
- int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
- EndpointPair endPoint = new EndpointPair(null, string.Empty, new HostName(host), port.ToString());
- this.sock = new DatagramSocket();
- this.sock.MessageReceived += this.OnMessageReceived;
- IAsyncAction result = this.sock.ConnectAsync(endPoint);
- result.Completed = this.OnConnected;
- this.DebugString += " End StartPing";
- return true;
- }
- }
- /// <summary>Check if done.</summary>
- public override bool Done()
- {
- lock (this.syncer)
- {
- return this.GotResult || this.sock == null; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
- }
- }
- /// <summary>Dispose of this ping.</summary>
- public override void Dispose()
- {
- lock (this.syncer)
- {
- this.sock = null;
- }
- }
- private void OnConnected(IAsyncAction asyncinfo, AsyncStatus asyncstatus)
- {
- lock (this.syncer)
- {
- if (asyncinfo.AsTask().IsCompleted && !asyncinfo.AsTask().IsFaulted && this.sock != null && this.sock.Information.RemoteAddress != null)
- {
- this.PingBytes[this.PingBytes.Length - 1] = this.PingId;
- DataWriter writer;
- writer = new DataWriter(this.sock.OutputStream);
- writer.WriteBytes(this.PingBytes);
- DataWriterStoreOperation res = writer.StoreAsync();
- res.AsTask().Wait(100);
- this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId + 1); // this buffer is re-used for the result/receive. invalidate the result now.
- writer.DetachStream();
- writer.Dispose();
- }
- else
- {
- this.sock = null; // will cause Done() to return true but this.Successful defines if the roundtrip completed
- }
- }
- }
- private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
- {
- lock (this.syncer)
- {
- DataReader reader = null;
- try
- {
- reader = args.GetDataReader();
- uint receivedByteCount = reader.UnconsumedBufferLength;
- if (receivedByteCount > 0)
- {
- byte[] resultBytes = new byte[receivedByteCount];
- reader.ReadBytes(resultBytes);
- //TODO: check result bytes!
- this.Successful = receivedByteCount == this.PingLength && resultBytes[resultBytes.Length - 1] == this.PingId;
- this.GotResult = true;
- }
- }
- catch
- {
- // TODO: handle error
- }
- }
- }
- }
- #endif
- #if NATIVE_SOCKETS
- /// <summary>Abstract base class to provide proper resource management for the below native ping implementations</summary>
- 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();
- }
- }
- /// <summary>Uses dynamic linked native Photon socket library via DllImport("PhotonSocketPlugin") attribute (as done by Unity Android and Unity PS3).</summary>
- 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
- /// <summary>Uses static linked native Photon socket library via DllImport("__Internal") attribute (as done by Unity iOS and Unity Switch).</summary>
- 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 UnityWebRequest webRequest;
- public override bool StartPing(string address)
- {
- base.Init();
- address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000);
- this.webRequest = UnityWebRequest.Get(address);
- this.webRequest.SendWebRequest();
- return true;
- }
- public override bool Done()
- {
- if (this.webRequest.isDone)
- {
- Successful = true;
- return true;
- }
- return false;
- }
- public override void Dispose()
- {
- this.webRequest.Dispose();
- }
- }
- #endif
- }
|