Created initial connection logic for server and client
This commit is contained in:
parent
ecbc3978f1
commit
f1406501c6
@ -20,6 +20,7 @@ namespace RBG_Server
|
|||||||
{
|
{
|
||||||
public GameServer BoardGameServer {get;}
|
public GameServer BoardGameServer {get;}
|
||||||
public CommunicationHandler communicationHandler { get; private set;}
|
public CommunicationHandler communicationHandler { get; private set;}
|
||||||
|
|
||||||
protected static readonly SolidColorBrush RED_BRUSH = new(Color.FromRgb(255, 0, 0));
|
protected static readonly SolidColorBrush RED_BRUSH = new(Color.FromRgb(255, 0, 0));
|
||||||
protected static readonly SolidColorBrush BLUE_BRUSH = new(Color.FromRgb(0, 0, 255));
|
protected static readonly SolidColorBrush BLUE_BRUSH = new(Color.FromRgb(0, 0, 255));
|
||||||
protected static readonly SolidColorBrush GREEN_BRUSH = new(Color.FromRgb(0, 255, 0));
|
protected static readonly SolidColorBrush GREEN_BRUSH = new(Color.FromRgb(0, 255, 0));
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
using System;
|
using NATUPNPLib;
|
||||||
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -10,8 +13,12 @@ namespace RBG_Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains all communication data, from both the client's and server's perspective (should be able to switch between each mode as necessary)
|
/// Contains all communication data, from both the client's and server's perspective (should be able to switch between each mode as necessary)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
///
|
||||||
public class CommunicationHandler
|
public class CommunicationHandler
|
||||||
{
|
{
|
||||||
|
UPnPNAT upnpnat = new();
|
||||||
|
IStaticPortMapping portMapping;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Image data is stored in memory in a dictionary collection. Each byte[] represents an image file, compressed according to its file type;
|
/// Image data is stored in memory in a dictionary collection. Each byte[] represents an image file, compressed according to its file type;
|
||||||
@ -20,5 +27,202 @@ namespace RBG_Server
|
|||||||
/// This limit can be changed at any time
|
/// This limit can be changed at any time
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConcurrentDictionary<string, CachedByteArray> ImageCollection { get; } = new();
|
public ConcurrentDictionary<string, CachedByteArray> ImageCollection { get; } = new();
|
||||||
|
public List<string> ImageList { get; } = new();
|
||||||
|
public string BoardName { get; set; }
|
||||||
|
|
||||||
|
public IPAddress IpAddress { get; set; }
|
||||||
|
public short Port { get; set; }
|
||||||
|
|
||||||
|
private TcpClient stateRetriever { get; set; }
|
||||||
|
private TcpClient dataRetriever { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public int ColumnCount { get;private set; }
|
||||||
|
public int RowCount { get;private set; }
|
||||||
|
public int ColumnZoomStart { get;private set; }
|
||||||
|
public int RowZoomStart { get;private set; }
|
||||||
|
public int ColumnZoomSpan { get;private set; }
|
||||||
|
public int RowZoomSpan { get;private set; }
|
||||||
|
public int StartingColumn { get;private set; }
|
||||||
|
public int StartingRow { get;private set; }
|
||||||
|
|
||||||
|
public void InitialiseServer()
|
||||||
|
{
|
||||||
|
// Find the server's active (reliable) network adapter, by creating a remote connection and retrieving our IP from it:
|
||||||
|
using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
|
||||||
|
{
|
||||||
|
s.Bind(new IPEndPoint(IPAddress.Any, 0));
|
||||||
|
s.Connect("microsoft.com", 0);
|
||||||
|
// The IP is implicitly the one assigned to the interface the OS would use to connect to the remote address
|
||||||
|
IpAddress = (s.LocalEndPoint as IPEndPoint).Address;
|
||||||
|
}
|
||||||
|
// Create the upnp mapping
|
||||||
|
upnpnat.StaticPortMappingCollection.Add(Port, "TCP", Port, IpAddress.ToString(), true, "RBGServer");
|
||||||
|
|
||||||
|
TcpListener listener = new(IPAddress.Any, Port); // Allow local comms
|
||||||
|
|
||||||
|
listener.Start();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
TcpClient client = listener.AcceptTcpClient();
|
||||||
|
// Delegate to another thread
|
||||||
|
_ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
AcceptConnections(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// At this point, the ImageCollection will have already been initialised.
|
||||||
|
// The name of the board will also be set to <boardname>{0}.<extension>.
|
||||||
|
// Server logic, such as accepting players and maintaining communication goes in here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptConnections(TcpClient client)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitialiseClient(TcpClient client)
|
||||||
|
{
|
||||||
|
// At this point, no details about the game are loaded; we must load them from the server (to which we have already connected [no data should have been sent]).
|
||||||
|
NetworkStream stateStream = stateRetriever.GetStream();
|
||||||
|
Task stateLoader = new Task(async () =>
|
||||||
|
{
|
||||||
|
// Get game board details
|
||||||
|
byte[] buffer = new byte[] { 1, 0, 0, 0, 1 };
|
||||||
|
stateStream.Write(buffer, 0, buffer.Length);
|
||||||
|
// Response size
|
||||||
|
var stateResponse = await GetResponse(stateStream, 4);
|
||||||
|
// Response data
|
||||||
|
stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); // Get the full response data
|
||||||
|
// Board state data
|
||||||
|
ColumnCount = GetInt32(stateResponse[..4]);
|
||||||
|
RowCount = GetInt32(stateResponse[4..8]);
|
||||||
|
ColumnZoomStart = GetInt32(stateResponse[8..12]);
|
||||||
|
RowZoomStart = GetInt32(stateResponse[12..16]);
|
||||||
|
ColumnZoomSpan = GetInt32(stateResponse[16..20]);
|
||||||
|
RowZoomSpan = GetInt32(stateResponse[20..24]);
|
||||||
|
StartingColumn = GetInt32(stateResponse[24..28]);
|
||||||
|
StartingRow = GetInt32(stateResponse[28..32]);
|
||||||
|
// Basic board data loaded; fetch players
|
||||||
|
buffer = new byte[] { 1, 0, 0, 0, 2};
|
||||||
|
stateStream.Write(buffer, 0, buffer.Length);
|
||||||
|
stateResponse = await GetResponse(stateStream, 4);
|
||||||
|
stateResponse = await GetResponse(stateStream, GetInt32(stateResponse));
|
||||||
|
// state response contains a player list
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetworkStream dataStream = dataRetriever.GetStream();
|
||||||
|
Task dataLoader = new Task(async () =>
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[] { 2, 0, 0, 0, 128, 0 }; // Get image collection names
|
||||||
|
dataStream.Write(buffer, 0, buffer.Length);
|
||||||
|
var dataResponse = await GetResponse(stateStream, 4);
|
||||||
|
|
||||||
|
dataResponse = await GetResponse(dataStream, GetInt32(dataResponse));
|
||||||
|
List<byte> data = new List<byte>();
|
||||||
|
for (int i = 0; i < dataResponse.Length; i++)
|
||||||
|
{
|
||||||
|
if (dataResponse[i] != 0)
|
||||||
|
{
|
||||||
|
data.Add(dataResponse[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImageList.Add(Encoding.UTF8.GetString(data.ToArray()));
|
||||||
|
data.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all low-resolution images
|
||||||
|
foreach (string item in ImageList)
|
||||||
|
{
|
||||||
|
byte[] strBytes = Encoding.ASCII.GetBytes(item);
|
||||||
|
buffer = new byte[strBytes.Length + 6];
|
||||||
|
byte[] lenBytes = GetBytes(strBytes.Length + 6);
|
||||||
|
lenBytes.CopyTo(buffer, 0);
|
||||||
|
buffer[4] = 129; // Download image
|
||||||
|
buffer[5] = 0; // mip_low
|
||||||
|
strBytes.CopyTo(buffer, 6);
|
||||||
|
dataStream.Write(buffer, 0, buffer.Length);
|
||||||
|
// Read the length, then the data
|
||||||
|
dataResponse = await GetResponse(dataStream);
|
||||||
|
dataResponse = await GetResponse(dataStream, GetInt32(dataResponse));
|
||||||
|
ImageCollection.TryAdd(item + "_mip_low", (CachedByteArray)dataResponse);
|
||||||
|
}
|
||||||
|
// At this point, the minimal amount of work required by the data thread has been done (load all thumbs)
|
||||||
|
// When an asset is needed from here, queue a load
|
||||||
|
});
|
||||||
|
|
||||||
|
dataLoader.Start();
|
||||||
|
stateLoader.Start();
|
||||||
|
|
||||||
|
|
||||||
|
byte[] buffer = new byte[] {0,0,0,1,1};
|
||||||
|
// Writing to the stream is to be considered near constant-time, but reading is non-constant.
|
||||||
|
// This application model must be synchronous, but we execute other commands before expecting our response to have arrived (it can complete at any time in that period)
|
||||||
|
stateStream.Write(buffer, 0, buffer.Length);
|
||||||
|
buffer[0] = 1;
|
||||||
|
buffer[3] = 0;
|
||||||
|
dataStream.Write(buffer, 0, buffer.Length);
|
||||||
|
// A details request is [<uint32, length>, <byte command>]
|
||||||
|
// The response is about the same format:
|
||||||
|
var stateResponse = GetResponse(stateStream, 4);
|
||||||
|
var dataResponse = GetResponse(dataStream, 4);
|
||||||
|
// First, load the board state (low mip-map, row definitions, column definitions, zoom position etc.)
|
||||||
|
// Retrieval command for the board
|
||||||
|
// Then load the player list, and use the low mip for their sprites
|
||||||
|
// Then check each loaded image and load the med, then large, then full sprite
|
||||||
|
// Then load the low of each unused image (for quick retrieval)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Idea is that each response is prefaced by a 4 byte stream length specifier.
|
||||||
|
/// This requires a busy wait to achieve, if not all recieved at once.
|
||||||
|
/// We retrieve the
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
/// <param name="targetLength"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<byte[]> GetResponse(NetworkStream stream, int targetLength=4)
|
||||||
|
{
|
||||||
|
if (stream.CanRead)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[targetLength];
|
||||||
|
StringBuilder myCompleteMessage = new StringBuilder();
|
||||||
|
int numberOfBytesRead = 0;
|
||||||
|
|
||||||
|
// Incoming message may be larger than the buffer size.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
numberOfBytesRead += await stream.ReadAsync(buffer, numberOfBytesRead, targetLength - numberOfBytesRead);
|
||||||
|
}
|
||||||
|
while (numberOfBytesRead < targetLength);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static internal byte[] GetBytes(int i)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(i);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
// We need data in big-endian format; so reverse the array
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static internal int GetInt32(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
// We need data in little-endian format; so reverse the array
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
}
|
||||||
|
return BitConverter.ToInt32(bytes, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,18 @@
|
|||||||
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
|
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<COMReference Include="NATUPNPLib">
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<Guid>1c565858-f302-471e-b409-f180aa4abec6</Guid>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<Isolated>false</Isolated>
|
||||||
|
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
|
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user