369 lines
13 KiB
C#
369 lines
13 KiB
C#
#if UNITY_WEBGL || WEBSOCKET || ((UNITY_XBOXONE || UNITY_GAMECORE) && UNITY_EDITOR)
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="SocketWebTcp.cs" company="Exit Games GmbH">
|
|
// Copyright (c) Exit Games GmbH. All rights reserved.
|
|
// </copyright>
|
|
// <summary>
|
|
// Internal class to encapsulate the network i/o functionality for the realtime library.
|
|
// </summary>
|
|
// <author>developer@exitgames.com</author>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
namespace ExitGames.Client.Photon
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using SupportClassPun = ExitGames.Client.Photon.SupportClass;
|
|
|
|
|
|
#if !(UNITY_WEBGL || NETFX_CORE)
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Yield Instruction to Wait for real seconds. Very important to keep connection working if Time.TimeScale is altered, we still want accurate network events
|
|
/// </summary>
|
|
public sealed class WaitForRealSeconds : CustomYieldInstruction
|
|
{
|
|
private readonly float _endTime;
|
|
|
|
public override bool keepWaiting
|
|
{
|
|
get { return this._endTime > Time.realtimeSinceStartup; }
|
|
}
|
|
|
|
public WaitForRealSeconds(float seconds)
|
|
{
|
|
this._endTime = Time.realtimeSinceStartup + seconds;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
|
/// </summary>
|
|
public class SocketWebTcp : IPhotonSocket, IDisposable
|
|
{
|
|
private WebSocket sock;
|
|
|
|
private readonly object syncer = new object();
|
|
|
|
public SocketWebTcp(PeerBase npeer) : base(npeer)
|
|
{
|
|
this.ServerAddress = npeer.ServerAddress;
|
|
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.INFO, "new SocketWebTcp() for Unity. Server: " + this.ServerAddress);
|
|
}
|
|
|
|
//this.Protocol = ConnectionProtocol.WebSocket;
|
|
this.PollReceive = false;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
this.State = PhotonSocketState.Disconnecting;
|
|
|
|
if (this.sock != null)
|
|
{
|
|
try
|
|
{
|
|
if (this.sock.Connected) this.sock.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.EnqueueDebugReturn(DebugLevel.INFO, "Exception in Dispose(): " + ex);
|
|
}
|
|
}
|
|
|
|
this.sock = null;
|
|
this.State = PhotonSocketState.Disconnected;
|
|
}
|
|
|
|
GameObject websocketConnectionObject;
|
|
public override bool Connect()
|
|
{
|
|
//bool baseOk = base.Connect();
|
|
//if (!baseOk)
|
|
//{
|
|
// return false;
|
|
//}
|
|
|
|
|
|
this.State = PhotonSocketState.Connecting;
|
|
|
|
if (this.websocketConnectionObject != null)
|
|
{
|
|
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
|
}
|
|
|
|
this.websocketConnectionObject = new GameObject("websocketConnectionObject");
|
|
MonoBehaviour mb = this.websocketConnectionObject.AddComponent<MonoBehaviourExt>();
|
|
this.websocketConnectionObject.hideFlags = HideFlags.HideInHierarchy;
|
|
UnityEngine.Object.DontDestroyOnLoad(this.websocketConnectionObject);
|
|
|
|
#if UNITY_WEBGL || NETFX_CORE
|
|
this.sock = new WebSocket(new Uri(this.ConnectAddress), this.SerializationProtocol);
|
|
this.sock.Connect();
|
|
|
|
mb.StartCoroutine(this.ReceiveLoop());
|
|
#else
|
|
|
|
mb.StartCoroutine(this.DetectIpVersionAndConnect(mb));
|
|
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
#if !(UNITY_WEBGL || NETFX_CORE)
|
|
private bool ipVersionDetectDone;
|
|
private IEnumerator DetectIpVersionAndConnect(MonoBehaviour mb)
|
|
{
|
|
Uri uri = null;
|
|
try
|
|
{
|
|
uri = new Uri(this.ConnectAddress);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ERROR, "Failed to create a URI from ConnectAddress (" + ConnectAddress + "). Exception: " + ex);
|
|
}
|
|
}
|
|
|
|
if (uri != null && uri.HostNameType == UriHostNameType.Dns)
|
|
{
|
|
ipVersionDetectDone = false;
|
|
|
|
ThreadPool.QueueUserWorkItem(this.DetectIpVersion, uri.Host);
|
|
|
|
while (!this.ipVersionDetectDone)
|
|
{
|
|
yield return new WaitForRealSeconds(0.1f);
|
|
}
|
|
}
|
|
|
|
if (this.AddressResolvedAsIpv6)
|
|
{
|
|
this.ConnectAddress += "&IPv6";
|
|
}
|
|
|
|
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.INFO, "DetectIpVersionAndConnect() AddressResolvedAsIpv6: " + this.AddressResolvedAsIpv6 + " ConnectAddress: " + ConnectAddress);
|
|
}
|
|
|
|
|
|
this.sock = new WebSocket(new Uri(this.ConnectAddress), this.SerializationProtocol);
|
|
this.sock.Connect();
|
|
|
|
mb.StartCoroutine(this.ReceiveLoop());
|
|
}
|
|
|
|
// state has to be the hostname string
|
|
private void DetectIpVersion(object state)
|
|
{
|
|
string host = state as string;
|
|
IPAddress[] ipAddresses;
|
|
try
|
|
{
|
|
ipAddresses = Dns.GetHostAddresses(host);
|
|
foreach (IPAddress ipAddress in ipAddresses)
|
|
{
|
|
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
|
{
|
|
this.AddressResolvedAsIpv6 = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.INFO, "DetectIpVersionAndConnect (uri: " + host + "= thread failed: " + ex);
|
|
}
|
|
|
|
this.ipVersionDetectDone = true;
|
|
}
|
|
#endif
|
|
|
|
|
|
public override bool Disconnect()
|
|
{
|
|
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcp.Disconnect()");
|
|
}
|
|
|
|
this.State = PhotonSocketState.Disconnecting;
|
|
|
|
lock (this.syncer)
|
|
{
|
|
if (this.sock != null)
|
|
{
|
|
try
|
|
{
|
|
this.sock.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ERROR, "Exception in Disconnect(): " + ex);
|
|
}
|
|
this.sock = null;
|
|
}
|
|
}
|
|
|
|
if (this.websocketConnectionObject != null)
|
|
{
|
|
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
|
}
|
|
|
|
this.State = PhotonSocketState.Disconnected;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// used by TPeer*
|
|
/// </summary>
|
|
public override PhotonSocketError Send(byte[] data, int length)
|
|
{
|
|
if (this.State != PhotonSocketState.Connected)
|
|
{
|
|
return PhotonSocketError.Skipped;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (data.Length > length)
|
|
{
|
|
byte[] trimmedData = new byte[length];
|
|
Buffer.BlockCopy(data, 0, trimmedData, 0, length);
|
|
data = trimmedData;
|
|
}
|
|
|
|
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ALL, "Sending: " + SupportClassPun.ByteArrayToString(data));
|
|
}
|
|
|
|
if (this.sock != null)
|
|
{
|
|
this.sock.Send(data);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ServerAddress + ". " + e.Message);
|
|
|
|
this.HandleException(StatusCode.Exception);
|
|
return PhotonSocketError.Exception;
|
|
}
|
|
|
|
return PhotonSocketError.Success;
|
|
}
|
|
|
|
public override PhotonSocketError Receive(out byte[] data)
|
|
{
|
|
data = null;
|
|
return PhotonSocketError.NoData;
|
|
}
|
|
|
|
|
|
internal const int ALL_HEADER_BYTES = 9;
|
|
internal const int TCP_HEADER_BYTES = 7;
|
|
internal const int MSG_HEADER_BYTES = 2;
|
|
|
|
public IEnumerator ReceiveLoop()
|
|
{
|
|
//this.Listener.DebugReturn(DebugLevel.INFO, "ReceiveLoop()");
|
|
if (this.sock != null)
|
|
{
|
|
while (this.sock != null && !this.sock.Connected && this.sock.Error == null)
|
|
{
|
|
yield return new WaitForRealSeconds(0.1f);
|
|
}
|
|
|
|
|
|
if (this.sock != null)
|
|
{
|
|
if (this.sock.Error != null)
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread. Server: " + this.ServerAddress + ":" + this.ServerPort + " Error: " + this.sock.Error);
|
|
this.HandleException(StatusCode.ExceptionOnConnect);
|
|
}
|
|
else
|
|
{
|
|
// connected
|
|
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ALL, "Receiving by websocket. this.State: " + this.State);
|
|
}
|
|
|
|
this.State = PhotonSocketState.Connected;
|
|
this.peerBase.OnConnect();
|
|
|
|
while (this.State == PhotonSocketState.Connected)
|
|
{
|
|
if (this.sock != null)
|
|
{
|
|
if (this.sock.Error != null)
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread (inside loop). Server: " + this.ServerAddress + ":" + this.ServerPort + " Error: " + this.sock.Error);
|
|
this.HandleException(StatusCode.ExceptionOnReceive);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
byte[] inBuff = this.sock.Recv();
|
|
if (inBuff == null || inBuff.Length == 0)
|
|
{
|
|
// nothing received. wait a bit, try again
|
|
yield return new WaitForRealSeconds(0.02f);
|
|
continue;
|
|
}
|
|
|
|
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
|
{
|
|
this.Listener.DebugReturn(DebugLevel.ALL, "TCP << " + inBuff.Length + " = " + SupportClassPun.ByteArrayToString(inBuff));
|
|
}
|
|
|
|
if (inBuff.Length > 0)
|
|
{
|
|
try
|
|
{
|
|
this.HandleReceivedDatagram(inBuff, inBuff.Length, false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
|
{
|
|
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
|
{
|
|
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ServerAddress + "' Exception: " + e);
|
|
}
|
|
|
|
this.HandleException(StatusCode.ExceptionOnReceive);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.Disconnect();
|
|
}
|
|
|
|
private class MonoBehaviourExt : MonoBehaviour { }
|
|
}
|
|
}
|
|
|
|
#endif |