189 lines
6.1 KiB
C#
189 lines
6.1 KiB
C#
using System;
|
|
using System.Net.Sockets;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using SBRL.Utilities;
|
|
using System.Text;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace PixelServer.Net
|
|
{
|
|
/// <summary>
|
|
/// Provides a mechanism to broadcast a program's presence on a multicast address and port.
|
|
/// </summary>
|
|
public class DiscoveryBeacon : IDisposable
|
|
{
|
|
TextWriter logger;
|
|
UdpClient beacon;
|
|
/// <summary>
|
|
/// Whether the beacon is currently active.
|
|
/// If true then the beacon needs disposing of properly by calling the Dispose() method before the program ends.
|
|
/// </summary>
|
|
public bool Active { get; private set; } = true;
|
|
|
|
string role = "server";
|
|
|
|
/// <summary>
|
|
/// The current IP address of the local machine.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Setting this value affects the IP address that is broadcast by the beacon.
|
|
/// </remarks>
|
|
public IPAddress LocalIP { get; set; }
|
|
/// <summary>
|
|
/// The (multicast) IP address that the beacon should broadcast to.
|
|
/// </summary>
|
|
public IPAddress DestinationAddress { get; private set; }
|
|
/// <summary>
|
|
/// The multicast endpoint that the beacon will broadcast to.
|
|
/// This value is dynamically generated from the DestinationAddress and Port properties.
|
|
/// </summary>
|
|
public IPEndPoint MulticastChannel { get; private set; }
|
|
/// <summary>
|
|
/// The port that the beacon should broadcast on.
|
|
/// </summary>
|
|
public int Port { get; set; } = 5050;
|
|
|
|
public string Role {
|
|
get{
|
|
return role;
|
|
}
|
|
private set {
|
|
if (value.Contains(" "))
|
|
throw new InvalidDataException($"Error: Invalid role '{value}'.");
|
|
role = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The frequency at which the beacon should emit broadcasts.
|
|
/// </summary>
|
|
public int EmitFrequency { get; set; } = 2000;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
|
|
/// </summary>
|
|
/// <param name="inDestAddress">The destination address to broadcast to.</param>
|
|
/// <param name="inPort">The port to broadcast on.</param>
|
|
/// <param name="inLogger">The place to send log messages to defaults to Console.Out.</param>
|
|
public DiscoveryBeacon(IPAddress inDestAddress, int inPort, TextWriter inLogger)
|
|
{
|
|
logger = inLogger;
|
|
|
|
Port = inPort;
|
|
DestinationAddress = inDestAddress;
|
|
MulticastChannel = new IPEndPoint(DestinationAddress, Port);
|
|
|
|
LocalIP = findLocalIP();
|
|
logger.WriteLine("Using {0} as the local IP Address.", LocalIP);
|
|
}
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
|
|
/// </summary>
|
|
/// <param name="inDestAddress">The destination address to broadcast to.</param>
|
|
/// <param name="inPort">The port to broadcast on.</param>
|
|
public DiscoveryBeacon(IPAddress inDestAddress, int inPort) : this(inDestAddress, inPort, Console.Out)
|
|
{
|
|
}
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PixelServer.Net.DiscoveryBeacon"/> class.
|
|
/// </summary>
|
|
/// <param name="inDestAddress">The destination address to broadcast to.</param>
|
|
public DiscoveryBeacon(IPAddress inDestAddress) : this(inDestAddress, 5050)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start the beacon up and broadcast the program's presence on a regular (See <see cref="EmitFrequency"/> basis.
|
|
/// </summary>
|
|
public async Task Emit()
|
|
{
|
|
connect();
|
|
|
|
while(true)
|
|
{
|
|
await emitOnce();
|
|
await Task.Delay(EmitFrequency);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the IP of the machine the beacon is running on.
|
|
/// </summary>
|
|
/// <returns>The first IP found that points to this machine.</returns>
|
|
private IPAddress findLocalIP()
|
|
{
|
|
List<IPAddress> localIPS = Utilities.GetLocalIps().ToList();
|
|
return localIPS[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connects the underlying UdpClient client.
|
|
/// </summary>
|
|
private void connect()
|
|
{
|
|
beacon = new UdpClient(Port);
|
|
Active = true;
|
|
beacon.JoinMulticastGroup(DestinationAddress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits a single presence broadcast from the beacon.
|
|
/// </summary>
|
|
private async Task emitOnce()
|
|
{
|
|
await Send($"{Role}@{LocalIP}:{Port}\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a message from the underlying UdpClient to the attached multicast address.
|
|
/// </summary>
|
|
/// <param name="data">The message to send.</param>
|
|
private async Task Send(string data)
|
|
{
|
|
await Send(MulticastChannel, data);
|
|
}
|
|
/// <summary>
|
|
/// Sends a message to the given endpoint using the underlying UdpClient.
|
|
/// </summary>
|
|
/// <param name="destination">The place to send the message to.</param>
|
|
/// <param name="data">The message payload.</param>
|
|
private async Task Send(IPEndPoint destination, string data)
|
|
{
|
|
logger.WriteLine("Sending '{0}' to {1}.", Utilities.EscapeString(data), destination);
|
|
byte[] payload = Encoding.UTF8.GetBytes(data);
|
|
await Send(destination, payload);
|
|
}
|
|
/// <summary>
|
|
/// Sends a raw byte array message to the given endpoint.
|
|
/// This is the lowest level Send() method before the message is passed to the UcpClient itself.
|
|
/// </summary>
|
|
/// <param name="destination">THe destination endpoint to send the message to.</param>
|
|
/// <param name="payload">The byte array to use as the message body.</param>
|
|
private async Task Send(IPEndPoint destination, byte[] payload)
|
|
{
|
|
if (beacon == null)
|
|
throw new InvalidOperationException("Error: Can't send data until you join a channel.");
|
|
await beacon.SendAsync(payload, payload.Length, MulticastChannel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases all resource used by the <see cref="PixelServer.Net.DiscoveryBeacon"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Call <see cref="Dispose"/> when you are finished using the <see cref="PixelServer.Net.DiscoveryBeacon"/>. The
|
|
/// <see cref="Dispose"/> method leaves the <see cref="PixelServer.Net.DiscoveryBeacon"/> in an unusable state. After
|
|
/// calling <see cref="Dispose"/>, you must release all references to the
|
|
/// <see cref="PixelServer.Net.DiscoveryBeacon"/> so the garbage collector can reclaim the memory that the
|
|
/// <see cref="PixelServer.Net.DiscoveryBeacon"/> was occupying.</remarks>
|
|
public void Dispose()
|
|
{
|
|
beacon.Close();
|
|
}
|
|
}
|
|
}
|
|
|