Seven is the number.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
15 KiB

3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="PhotonPing.cs" company="Exit Games GmbH">
  3. // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // This file includes various PhotonPing implementations for different APIs,
  7. // platforms and protocols.
  8. // The RegionPinger class is the instance which selects the Ping implementation
  9. // to use.
  10. // </summary>
  11. // <author>developer@exitgames.com</author>
  12. // ----------------------------------------------------------------------------
  13. namespace Photon.Realtime
  14. {
  15. using System;
  16. using System.Collections;
  17. using System.Threading;
  18. #if NETFX_CORE
  19. using System.Diagnostics;
  20. using Windows.Foundation;
  21. using Windows.Networking;
  22. using Windows.Networking.Sockets;
  23. using Windows.Storage.Streams;
  24. #endif
  25. #if !NO_SOCKET && !NETFX_CORE
  26. using System.Collections.Generic;
  27. using System.Diagnostics;
  28. using System.Net.Sockets;
  29. #endif
  30. #if UNITY_WEBGL
  31. // import WWW class
  32. using UnityEngine;
  33. #endif
  34. /// <summary>
  35. /// Abstract implementation of PhotonPing, ase for pinging servers to find the "Best Region".
  36. /// </summary>
  37. public abstract class PhotonPing : IDisposable
  38. {
  39. public string DebugString = "";
  40. public bool Successful;
  41. protected internal bool GotResult;
  42. protected internal int PingLength = 13;
  43. protected internal byte[] PingBytes = new byte[] { 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x00 };
  44. protected internal byte PingId;
  45. private static readonly System.Random RandomIdProvider = new System.Random();
  46. public virtual bool StartPing(string ip)
  47. {
  48. throw new NotImplementedException();
  49. }
  50. public virtual bool Done()
  51. {
  52. throw new NotImplementedException();
  53. }
  54. public virtual void Dispose()
  55. {
  56. throw new NotImplementedException();
  57. }
  58. protected internal void Init()
  59. {
  60. this.GotResult = false;
  61. this.Successful = false;
  62. this.PingId = (byte)(RandomIdProvider.Next(255));
  63. }
  64. }
  65. #if !NETFX_CORE && !NO_SOCKET
  66. /// <summary>Uses C# Socket class from System.Net.Sockets (as Unity usually does).</summary>
  67. /// <remarks>Incompatible with Windows 8 Store/Phone API.</remarks>
  68. public class PingMono : PhotonPing
  69. {
  70. private Socket sock;
  71. /// <summary>
  72. /// Sends a "Photon Ping" to a server.
  73. /// </summary>
  74. /// <param name="ip">Address in IPv4 or IPv6 format. An address containing a '.' will be interpreted as IPv4.</param>
  75. /// <returns>True if the Photon Ping could be sent.</returns>
  76. public override bool StartPing(string ip)
  77. {
  78. this.Init();
  79. try
  80. {
  81. if (this.sock == null)
  82. {
  83. if (ip.Contains("."))
  84. {
  85. this.sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  86. }
  87. else
  88. {
  89. this.sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
  90. }
  91. this.sock.ReceiveTimeout = 5000;
  92. int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
  93. this.sock.Connect(ip, port);
  94. }
  95. this.PingBytes[this.PingBytes.Length - 1] = this.PingId;
  96. this.sock.Send(this.PingBytes);
  97. this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId+1); // this buffer is re-used for the result/receive. invalidate the result now.
  98. }
  99. catch (Exception e)
  100. {
  101. this.sock = null;
  102. Console.WriteLine(e);
  103. }
  104. return false;
  105. }
  106. public override bool Done()
  107. {
  108. if (this.GotResult || this.sock == null)
  109. {
  110. return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
  111. }
  112. int read = 0;
  113. try
  114. {
  115. if (!this.sock.Poll(0, SelectMode.SelectRead))
  116. {
  117. return false;
  118. }
  119. read = this.sock.Receive(this.PingBytes, SocketFlags.None);
  120. }
  121. catch (Exception ex)
  122. {
  123. if (this.sock != null)
  124. {
  125. this.sock.Close();
  126. this.sock = null;
  127. }
  128. this.DebugString += " Exception of socket! " + ex.GetType() + " ";
  129. return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
  130. }
  131. bool replyMatch = this.PingBytes[this.PingBytes.Length - 1] == this.PingId && read == this.PingLength;
  132. if (!replyMatch)
  133. {
  134. this.DebugString += " ReplyMatch is false! ";
  135. }
  136. this.Successful = replyMatch;
  137. this.GotResult = true;
  138. return true;
  139. }
  140. public override void Dispose()
  141. {
  142. try
  143. {
  144. this.sock.Close();
  145. }
  146. catch
  147. {
  148. }
  149. this.sock = null;
  150. }
  151. }
  152. #endif
  153. #if NETFX_CORE
  154. /// <summary>Windows store API implementation of PhotonPing, based on DatagramSocket for UDP.</summary>
  155. public class PingWindowsStore : PhotonPing
  156. {
  157. private DatagramSocket sock;
  158. private readonly object syncer = new object();
  159. public override bool StartPing(string host)
  160. {
  161. lock (this.syncer)
  162. {
  163. this.Init();
  164. int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
  165. EndpointPair endPoint = new EndpointPair(null, string.Empty, new HostName(host), port.ToString());
  166. this.sock = new DatagramSocket();
  167. this.sock.MessageReceived += this.OnMessageReceived;
  168. IAsyncAction result = this.sock.ConnectAsync(endPoint);
  169. result.Completed = this.OnConnected;
  170. this.DebugString += " End StartPing";
  171. return true;
  172. }
  173. }
  174. public override bool Done()
  175. {
  176. lock (this.syncer)
  177. {
  178. return this.GotResult || this.sock == null; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
  179. }
  180. }
  181. public override void Dispose()
  182. {
  183. lock (this.syncer)
  184. {
  185. this.sock = null;
  186. }
  187. }
  188. private void OnConnected(IAsyncAction asyncinfo, AsyncStatus asyncstatus)
  189. {
  190. lock (this.syncer)
  191. {
  192. if (asyncinfo.AsTask().IsCompleted && !asyncinfo.AsTask().IsFaulted && this.sock != null && this.sock.Information.RemoteAddress != null)
  193. {
  194. this.PingBytes[this.PingBytes.Length - 1] = this.PingId;
  195. DataWriter writer;
  196. writer = new DataWriter(this.sock.OutputStream);
  197. writer.WriteBytes(this.PingBytes);
  198. DataWriterStoreOperation res = writer.StoreAsync();
  199. res.AsTask().Wait(100);
  200. this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId + 1); // this buffer is re-used for the result/receive. invalidate the result now.
  201. writer.DetachStream();
  202. writer.Dispose();
  203. }
  204. else
  205. {
  206. this.sock = null; // will cause Done() to return true but this.Successful defines if the roundtrip completed
  207. }
  208. }
  209. }
  210. private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
  211. {
  212. lock (this.syncer)
  213. {
  214. DataReader reader = null;
  215. try
  216. {
  217. reader = args.GetDataReader();
  218. uint receivedByteCount = reader.UnconsumedBufferLength;
  219. if (receivedByteCount > 0)
  220. {
  221. byte[] resultBytes = new byte[receivedByteCount];
  222. reader.ReadBytes(resultBytes);
  223. //TODO: check result bytes!
  224. this.Successful = receivedByteCount == this.PingLength && resultBytes[resultBytes.Length - 1] == this.PingId;
  225. this.GotResult = true;
  226. }
  227. }
  228. catch
  229. {
  230. // TODO: handle error
  231. }
  232. }
  233. }
  234. }
  235. #endif
  236. #if NATIVE_SOCKETS
  237. /// <summary>Abstract base class to provide proper resource management for the below native ping implementations</summary>
  238. public abstract class PingNative : PhotonPing
  239. {
  240. // Native socket states - according to EnetConnect.h state definitions
  241. protected enum NativeSocketState : byte
  242. {
  243. Disconnected = 0,
  244. Connecting = 1,
  245. Connected = 2,
  246. ConnectionError = 3,
  247. SendError = 4,
  248. ReceiveError = 5,
  249. Disconnecting = 6
  250. }
  251. protected IntPtr pConnectionHandler = IntPtr.Zero;
  252. ~PingNative()
  253. {
  254. Dispose();
  255. }
  256. }
  257. /// <summary>Uses dynamic linked native Photon socket library via DllImport("PhotonSocketPlugin") attribute (as done by Unity Android and Unity PS3).</summary>
  258. public class PingNativeDynamic : PingNative
  259. {
  260. public override bool StartPing(string ip)
  261. {
  262. lock (SocketUdpNativeDynamic.syncer)
  263. {
  264. base.Init();
  265. if(pConnectionHandler == IntPtr.Zero)
  266. {
  267. pConnectionHandler = SocketUdpNativeDynamic.egconnect(ip);
  268. SocketUdpNativeDynamic.egservice(pConnectionHandler);
  269. byte state = SocketUdpNativeDynamic.eggetState(pConnectionHandler);
  270. while (state == (byte) NativeSocketState.Connecting)
  271. {
  272. SocketUdpNativeDynamic.egservice(pConnectionHandler);
  273. state = SocketUdpNativeDynamic.eggetState(pConnectionHandler);
  274. }
  275. }
  276. PingBytes[PingBytes.Length - 1] = PingId;
  277. SocketUdpNativeDynamic.egsend(pConnectionHandler, PingBytes, PingBytes.Length);
  278. SocketUdpNativeDynamic.egservice(pConnectionHandler);
  279. PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1);
  280. return true;
  281. }
  282. }
  283. public override bool Done()
  284. {
  285. lock (SocketUdpNativeDynamic.syncer)
  286. {
  287. if (this.GotResult || pConnectionHandler == IntPtr.Zero)
  288. {
  289. return true;
  290. }
  291. int available = SocketUdpNativeDynamic.egservice(pConnectionHandler);
  292. if (available < PingLength)
  293. {
  294. return false;
  295. }
  296. int pingBytesLength = PingBytes.Length;
  297. int bytesInRemainginDatagrams = SocketUdpNativeDynamic.egread(pConnectionHandler, PingBytes, ref pingBytesLength);
  298. this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId);
  299. //Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId);
  300. this.GotResult = true;
  301. return true;
  302. }
  303. }
  304. public override void Dispose()
  305. {
  306. lock (SocketUdpNativeDynamic.syncer)
  307. {
  308. if (this.pConnectionHandler != IntPtr.Zero)
  309. SocketUdpNativeDynamic.egdisconnect(this.pConnectionHandler);
  310. this.pConnectionHandler = IntPtr.Zero;
  311. }
  312. GC.SuppressFinalize(this);
  313. }
  314. }
  315. #if NATIVE_SOCKETS && NATIVE_SOCKETS_STATIC
  316. /// <summary>Uses static linked native Photon socket library via DllImport("__Internal") attribute (as done by Unity iOS and Unity Switch).</summary>
  317. public class PingNativeStatic : PingNative
  318. {
  319. public override bool StartPing(string ip)
  320. {
  321. base.Init();
  322. lock (SocketUdpNativeStatic.syncer)
  323. {
  324. if(pConnectionHandler == IntPtr.Zero)
  325. {
  326. pConnectionHandler = SocketUdpNativeStatic.egconnect(ip);
  327. SocketUdpNativeStatic.egservice(pConnectionHandler);
  328. byte state = SocketUdpNativeStatic.eggetState(pConnectionHandler);
  329. while (state == (byte) NativeSocketState.Connecting)
  330. {
  331. SocketUdpNativeStatic.egservice(pConnectionHandler);
  332. state = SocketUdpNativeStatic.eggetState(pConnectionHandler);
  333. Thread.Sleep(0); // suspending execution for a moment is critical on Switch for the OS to update the socket
  334. }
  335. }
  336. PingBytes[PingBytes.Length - 1] = PingId;
  337. SocketUdpNativeStatic.egsend(pConnectionHandler, PingBytes, PingBytes.Length);
  338. SocketUdpNativeStatic.egservice(pConnectionHandler);
  339. PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1);
  340. return true;
  341. }
  342. }
  343. public override bool Done()
  344. {
  345. lock (SocketUdpNativeStatic.syncer)
  346. {
  347. if (this.GotResult || pConnectionHandler == IntPtr.Zero)
  348. {
  349. return true;
  350. }
  351. int available = SocketUdpNativeStatic.egservice(pConnectionHandler);
  352. if (available < PingLength)
  353. {
  354. return false;
  355. }
  356. int pingBytesLength = PingBytes.Length;
  357. int bytesInRemainginDatagrams = SocketUdpNativeStatic.egread(pConnectionHandler, PingBytes, ref pingBytesLength);
  358. this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId);
  359. //Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId);
  360. this.GotResult = true;
  361. return true;
  362. }
  363. }
  364. public override void Dispose()
  365. {
  366. lock (SocketUdpNativeStatic.syncer)
  367. {
  368. if (pConnectionHandler != IntPtr.Zero)
  369. SocketUdpNativeStatic.egdisconnect(pConnectionHandler);
  370. pConnectionHandler = IntPtr.Zero;
  371. }
  372. GC.SuppressFinalize(this);
  373. }
  374. }
  375. #endif
  376. #endif
  377. #if UNITY_WEBGL
  378. public class PingHttp : PhotonPing
  379. {
  380. private WWW webRequest;
  381. public override bool StartPing(string address)
  382. {
  383. base.Init();
  384. address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000);
  385. this.webRequest = new WWW(address);
  386. return true;
  387. }
  388. public override bool Done()
  389. {
  390. if (this.webRequest.isDone)
  391. {
  392. Successful = true;
  393. return true;
  394. }
  395. return false;
  396. }
  397. public override void Dispose()
  398. {
  399. this.webRequest.Dispose();
  400. }
  401. }
  402. #endif
  403. }