PixelServer -> PixelHub
This commit is contained in:
parent
9c545a6d2a
commit
0756ac1986
12 changed files with 342 additions and 3 deletions
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelServer", "PixelServer\PixelServer.csproj", "{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelHub", "PixelHub\PixelHub.csproj", "{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
211
PixelHub/DiscoveryBeacon.cs
Normal file
211
PixelHub/DiscoveryBeacon.cs
Normal file
|
@ -0,0 +1,211 @@
|
|||
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 PixelHub.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;
|
||||
int emitFrequency = 2000;
|
||||
string role = "server";
|
||||
|
||||
/// <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;
|
||||
/// <summary>
|
||||
/// Whether the logging output should be verbose.
|
||||
/// </summary>
|
||||
public bool Verbose { get; private set; } = false;
|
||||
|
||||
|
||||
/// <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;
|
||||
/// <summary>
|
||||
/// The role that the program hosted at this beacon plays. Default: server.
|
||||
/// </summary>
|
||||
public string Role {
|
||||
get{
|
||||
return role;
|
||||
}
|
||||
private set {
|
||||
if (value.Contains(" "))
|
||||
throw new InvalidDataException($"Error: Invalid role '{value}'.");
|
||||
role = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time, in milliseconds, that the beacon should wait between broadcasts.
|
||||
/// </summary>
|
||||
public int EmitFrequency {
|
||||
get {
|
||||
return emitFrequency;
|
||||
}
|
||||
set {
|
||||
emitFrequency = value;
|
||||
logger.WriteLine("Beacon broadcast frequency changed to {0}ms.", EmitFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PixelHub.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="PixelHub.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="PixelHub.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();
|
||||
logger.WriteLine("Beacon activated with broadcast interval of {0}ms.", EmitFrequency);
|
||||
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);
|
||||
logger.WriteLine("One way beacon set up pointing at {0}.", MulticastChannel);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if(Verbose) 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="PixelHub.Net.DiscoveryBeacon"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call <see cref="Dispose"/> when you are finished using the <see cref="PixelHub.Net.DiscoveryBeacon"/>. The
|
||||
/// <see cref="Dispose"/> method leaves the <see cref="PixelHub.Net.DiscoveryBeacon"/> in an unusable state. After
|
||||
/// calling <see cref="Dispose"/>, you must release all references to the
|
||||
/// <see cref="PixelHub.Net.DiscoveryBeacon"/> so the garbage collector can reclaim the memory that the
|
||||
/// <see cref="PixelHub.Net.DiscoveryBeacon"/> was occupying.</remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if(Active)
|
||||
{
|
||||
beacon.Close();
|
||||
Active = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
94
PixelHub/PixelHub.cs
Normal file
94
PixelHub/PixelHub.cs
Normal file
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using SBRL.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace PixelHub
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the server component of the Hull Pixelbot server that can be used to control any number of pixel bots at once.
|
||||
/// </summary>
|
||||
public class PixelHub
|
||||
{
|
||||
TextWriter logger;
|
||||
IPAddress bindAddress = IPAddress.Any;
|
||||
int port;
|
||||
|
||||
TcpListener server;
|
||||
/// <summary>
|
||||
/// Whether the Hull Pixelbot serve is currently active and listening.
|
||||
/// </summary>
|
||||
public bool Active { get; private set; } = false;
|
||||
public IPAddress BindAddress
|
||||
{
|
||||
get {
|
||||
return bindAddress;
|
||||
}
|
||||
set {
|
||||
if (Active == true)
|
||||
throw new InvalidOperationException("Error: The bind address can't be changed once the server has started listening!");
|
||||
bindAddress = value;
|
||||
}
|
||||
}
|
||||
public int Port
|
||||
{
|
||||
get {
|
||||
return port;
|
||||
}
|
||||
set {
|
||||
if (Active == true)
|
||||
throw new InvalidOperationException("Error: The port can't be changed once the server has started listening!");
|
||||
port = value;
|
||||
}
|
||||
}
|
||||
public IPEndPoint Endpoint
|
||||
{
|
||||
get {
|
||||
return new IPEndPoint(BindAddress, Port);
|
||||
}
|
||||
}
|
||||
|
||||
public PixelHub(int inPort, TextWriter inLogger)
|
||||
{
|
||||
Port = inPort;
|
||||
logger = inLogger;
|
||||
}
|
||||
public PixelHub(int inPort) : this(inPort, Console.Out)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task Listen()
|
||||
{
|
||||
server = new TcpListener(Endpoint);
|
||||
logger.WriteLine("TCP Listener set up on {0}.", Endpoint);
|
||||
Active = true;
|
||||
while(true)
|
||||
{
|
||||
TcpClient nextClient = await server.AcceptTcpClientAsync();
|
||||
AsyncTools.ForgetTask(Handle(nextClient));
|
||||
}
|
||||
// TODO: Add an shutdown CancellationToken thingy here
|
||||
//Active = false;
|
||||
}
|
||||
|
||||
private async Task Handle(TcpClient client)
|
||||
{
|
||||
logger.WriteLine("Accepted connection from {0}.", client.Client.RemoteEndPoint);
|
||||
|
||||
using (StreamReader incoming = new StreamReader(client.GetStream()))
|
||||
using (StreamWriter outgoing = new StreamWriter(client.GetStream()) { AutoFlush = true })
|
||||
{
|
||||
string nextLine;
|
||||
while((nextLine = await incoming.ReadLineAsync()) != null)
|
||||
{
|
||||
Console.WriteLine("Got message from client: '{0}'", nextLine.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Lost connection from {0}.", client.Client.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +38,8 @@
|
|||
<Compile Include="DiscoveryBeacon.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
<Compile Include="ForgetTask.cs" />
|
||||
<Compile Include="PixelServer.cs" />
|
||||
<Compile Include="PixelHub.cs" />
|
||||
<Compile Include="PrefixedWriter.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
34
PixelHub/Program.cs
Normal file
34
PixelHub/Program.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SBRL.Utilities;
|
||||
using PixelHub.Net;
|
||||
|
||||
namespace PixelHub
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
private static int port = 5050;
|
||||
private static PrefixedWriter systemWriter = new PrefixedWriter(Console.Out) { Prefix = "[System] " };
|
||||
private static PrefixedWriter beaconWriter = new PrefixedWriter(Console.Out) { Prefix = "[Beacon] " };
|
||||
private static PrefixedWriter serverWriter = new PrefixedWriter(Console.Out) { Prefix = "[Server] " };
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
systemWriter.WriteLine("Booting server.");
|
||||
DiscoveryBeacon beacon = new DiscoveryBeacon(
|
||||
IPAddress.Parse("239.62.148.30"), port,
|
||||
beaconWriter
|
||||
);
|
||||
PixelHub server = new PixelHub(port, serverWriter);
|
||||
systemWriter.WriteLine("Server booting complete. Beginning async loop.");
|
||||
//AsyncTools.ForgetTask(beacon.Emit());
|
||||
Task.WaitAll(new Task[] {
|
||||
beacon.Emit(),
|
||||
server.Listen()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue