using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Transporting; using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using UnityEngine; namespace FishNet.Discovery { /// /// A component that advertises a server or searches for servers. /// public sealed class NetworkDiscovery : MonoBehaviour { /// /// A string that differentiates your application/game from others. /// Must not be null, empty, or blank. /// [SerializeField] [Tooltip("A string that differentiates your application/game from others. Must not be null, empty, or blank.")] private string secret; /// /// The port number used by this component. /// Must be different from the one used by the . /// [SerializeField] [Tooltip("The port number used by this NetworkDiscovery component. Must be different from the one used by the Transport.")] private ushort port; /// /// How often does this component advertises a server or searches for servers. /// [SerializeField] [Tooltip("How often does this NetworkDiscovery component advertises a server or searches for servers.")] private float discoveryInterval; /// /// Whether this component will automatically start/stop? Setting this to true is recommended. /// [SerializeField] [Tooltip("Whether this NetworkDiscovery component will automatically start/stop? Setting this to true is recommended.")] private bool automatic; /// /// The used to advertise the server. /// private UdpClient _serverUdpClient; /// /// The used to search for servers. /// private UdpClient _clientUdpClient; /// /// Whether this component is currently advertising a server or not. /// public bool IsAdvertising => _serverUdpClient != null; /// /// Whether this component is currently searching for servers or not. /// public bool IsSearching => _clientUdpClient != null; /// /// An that is invoked by this component whenever a server is found. /// public event Action ServerFoundCallback; private void Start() { if (automatic) { InstanceFinder.ServerManager.OnServerConnectionState += ServerConnectionStateChangedHandler; InstanceFinder.ClientManager.OnClientConnectionState += ClientConnectionStateChangedHandler; StartSearchingForServers(); } } private void OnDisable() { InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler; InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler; StopAdvertisingServer(); StopSearchingForServers(); } private void OnDestroy() { InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler; InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler; StopAdvertisingServer(); StopSearchingForServers(); } private void OnApplicationQuit() { InstanceFinder.ServerManager.OnServerConnectionState -= ServerConnectionStateChangedHandler; InstanceFinder.ClientManager.OnClientConnectionState -= ClientConnectionStateChangedHandler; StopAdvertisingServer(); StopSearchingForServers(); } #region Connection State Handlers private void ServerConnectionStateChangedHandler(ServerConnectionStateArgs args) { if (args.ConnectionState == LocalConnectionState.Starting) { StopSearchingForServers(); } else if (args.ConnectionState == LocalConnectionState.Started) { StartAdvertisingServer(); } else if (args.ConnectionState == LocalConnectionState.Stopping) { //StopAdvertisingServer(); } else if (args.ConnectionState == LocalConnectionState.Stopped) { StartSearchingForServers(); } } private void ClientConnectionStateChangedHandler(ClientConnectionStateArgs args) { if (args.ConnectionState == LocalConnectionState.Starting) { StopSearchingForServers(); } else if (args.ConnectionState == LocalConnectionState.Stopped) { StartSearchingForServers(); } } #endregion #region Server /// /// Makes this component start advertising a server. /// public void StartAdvertisingServer() { //Debug.Log("NetworkDiscovery is advertizing on port " + port); if (!InstanceFinder.IsServer) { if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start advertising server. Server is inactive.", this); return; } if (_serverUdpClient != null) { if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Server is already being advertised.", this); return; } if (port == InstanceFinder.TransportManager.Transport.GetPort()) { if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start advertising server on the same port as the transport.", this); return; } _serverUdpClient = new UdpClient(port) { EnableBroadcast = true, MulticastLoopback = false, }; //Debug.Log("UDP = " + _serverUdpClient.ToString()); Task.Run(AdvertiseServerAsync); if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Started advertising server.", this); } /// /// Makes this component immediately stop advertising the server it is currently advertising. /// public void StopAdvertisingServer() { if (_serverUdpClient == null) return; _serverUdpClient.Close(); _serverUdpClient = null; if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Stopped advertising server.", this); } private async void AdvertiseServerAsync() { while (_serverUdpClient != null) { await Task.Delay(TimeSpan.FromSeconds(discoveryInterval)); UdpReceiveResult result = await _serverUdpClient.ReceiveAsync(); Debug.Log("Awaiting for server"); string receivedSecret = Encoding.UTF8.GetString(result.Buffer); Debug.Log(receivedSecret); if (receivedSecret == secret) { byte[] okBytes = BitConverter.GetBytes(true); await _serverUdpClient.SendAsync(okBytes, okBytes.Length, result.RemoteEndPoint); } } Debug.Log("_serverUdpClient == null"); } #endregion #region Client /// /// Makes this component start searching for servers. /// public void StartSearchingForServers() { // if (InstanceFinder.IsServer) // { // if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start searching for servers. Server is active.", this); // // return; // } Debug.Log("NetworkDiscovery is searching"); if (InstanceFinder.IsClient) { if (NetworkManager.StaticCanLog(LoggingType.Warning)) Debug.LogWarning("Unable to start searching for servers. Client is active.", this); return; } if (_clientUdpClient != null) { if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Already searching for servers.", this); return; } _clientUdpClient = new UdpClient() { EnableBroadcast = true, MulticastLoopback = false, }; Task.Run(SearchForServersAsync); if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Started searching for servers.", this); } /// /// Makes this component immediately stop searching for servers. /// public void StopSearchingForServers() { if (_clientUdpClient == null) return; _clientUdpClient.Close(); _clientUdpClient = null; if (NetworkManager.StaticCanLog(LoggingType.Common)) Debug.Log("Stopped searching for servers.", this); } private async void SearchForServersAsync() { byte[] secretBytes = Encoding.UTF8.GetBytes(secret); IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, port); while (_clientUdpClient != null) { await Task.Delay(TimeSpan.FromSeconds(discoveryInterval)); await _clientUdpClient.SendAsync(secretBytes, secretBytes.Length, endPoint); UdpReceiveResult result = await _clientUdpClient.ReceiveAsync(); if (BitConverter.ToBoolean(result.Buffer, 0)) { ServerFoundCallback?.Invoke(result.RemoteEndPoint); StopSearchingForServers(); } } } #endregion } }