Re-add Hazel client

This commit is contained in:
AeonLucid committed 2020-10-19 16:18:49 +02:00
1 parent 503cd221d5
commit f906b010db
11 files changed
+413 -27

No files matched your search

@@ -7,12 +7,16 @@ namespace Impostor.Api.Net.Messages.C2S
{
public static void Serialize(IMessageWriter writer, GameOptionsData gameOptionsData)
{
writer.StartMessage(MessageFlags.HostGame);
using (var memory = new MemoryStream())
using (var writerBin = new BinaryWriter(memory))
{
gameOptionsData.Serialize(writerBin, GameOptionsData.LatestVersion);
writer.WriteBytesAndSize(memory.ToArray());
}
writer.EndMessage();
}
public static GameOptionsData Deserialize(IMessageReader reader)
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Impostor.Client\Impostor.Client.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>
</Project>
+70
View File
@@ -0,0 +1,70 @@
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Hazel;
using Hazel.Udp;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Messages;
using Impostor.Api.Net.Messages.C2S;
using Serilog;
namespace Impostor.Client.App
{
internal static class Program
{
private static readonly ManualResetEvent QuitEvent = new ManualResetEvent(false);
private static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var writeHandshake = MessageWriter.Get(MessageType.Reliable);
writeHandshake.Write(50516550);
writeHandshake.Write("AeonLucid");
var writeGameCreate = MessageWriter.Get(MessageType.Reliable);
Message00HostGameC2S.Serialize(writeGameCreate, new GameOptionsData
{
MaxPlayers = 4,
NumImpostors = 2
});
using (var connection = new UdpClientConnection(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 22023)))
{
var e = new ManualResetEvent(false);
// Register events.
connection.DataReceived = DataReceived;
connection.Disconnected = Disconnected;
// Connect and send handshake.
await connection.ConnectAsync(writeHandshake.ToByteArray(false));
Log.Information("Connected.");
// Create a game.
await connection.Send(writeGameCreate);
Log.Information("Requested game creation.");
e.WaitOne();
}
}
private static ValueTask DataReceived(DataReceivedEventArgs e)
{
Log.Information("Received data.");
return default;
}
private static ValueTask Disconnected(DisconnectedEventArgs e)
{
Log.Information("Disconnected: " + e.Reason);
QuitEvent.Set();
return default;
}
}
}
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
<ProjectReference Include="..\Impostor.Hazel\Impostor.Hazel.csproj" />
</ItemGroup>
</Project>
+1 -9
View File
@@ -155,21 +155,13 @@ namespace Hazel
/// </para>
/// </remarks>
public abstract ValueTask SendBytes(byte[] bytes, MessageType messageType = MessageType.Unreliable);
/// <summary>
/// Connects the connection to a server and begins listening.
/// This method blocks and may thrown if there is a problem connecting.
/// </summary>
/// <param name="bytes">The bytes of data to send in the handshake.</param>
/// <param name="timeout">The number of milliseconds to wait before giving up on the connect attempt.</param>
public abstract void Connect(byte[] bytes = null, int timeout = 5000);
/// <summary>
/// Connects the connection to a server and begins listening.
/// This method does not block.
/// </summary>
/// <param name="bytes">The bytes of data to send in the handshake.</param>
public abstract void ConnectAsync(byte[] bytes = null);
public abstract ValueTask ConnectAsync(byte[] bytes = null);
/// <summary>
/// Invokes the DataReceived event.
+3 -3
View File
@@ -52,14 +52,14 @@ namespace Hazel
/// <summary>
/// Sends a disconnect message to the end point.
/// </summary>
protected abstract bool SendDisconnect(MessageWriter writer);
protected abstract ValueTask<bool> SendDisconnect(MessageWriter writer);
/// <summary>
/// Called when the socket has been disconnected at the remote host.
/// </summary>
protected async ValueTask DisconnectRemote(string reason, IMessageReader reader)
{
if (this.SendDisconnect(null))
if (await SendDisconnect(null))
{
try
{
@@ -107,7 +107,7 @@ namespace Hazel
/// </summary>
public override async ValueTask Disconnect(string reason, MessageWriter writer = null)
{
if (this.SendDisconnect(writer))
if (await SendDisconnect(writer))
{
try
{
@@ -0,0 +1,252 @@
using System;
using System.Buffers;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Impostor.Api.Net.Messages;
using Serilog;
namespace Hazel.Udp
{
/// <summary>
/// Represents a client's connection to a server that uses the UDP protocol.
/// </summary>
/// <inheritdoc/>
public sealed class UdpClientConnection : UdpConnection
{
private static readonly ILogger Logger = Log.ForContext<UdpClientConnection>();
/// <summary>
/// The socket we're connected via.
/// </summary>
private readonly UdpClient _socket;
private readonly Timer _reliablePacketTimer;
private readonly SemaphoreSlim _connectWaitLock;
private readonly MemoryPool<byte> _pool;
private readonly Channel<MessageData> _channel;
private Task _listenTask;
private Task _handleTask;
/// <summary>
/// Creates a new UdpClientConnection.
/// </summary>
/// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
public UdpClientConnection(IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4) : base(null)
{
EndPoint = remoteEndPoint;
RemoteEndPoint = remoteEndPoint;
IPMode = ipMode;
_socket = new UdpClient
{
DontFragment = false
};
_reliablePacketTimer = new Timer(ManageReliablePacketsInternal, null, 100, Timeout.Infinite);
_connectWaitLock = new SemaphoreSlim(1, 1);
_pool = MemoryPool<byte>.Shared;
_channel = Channel.CreateUnbounded<MessageData>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = true
});
}
~UdpClientConnection()
{
Dispose(false);
}
private async void ManageReliablePacketsInternal(object state)
{
await ManageReliablePackets();
try
{
_reliablePacketTimer.Change(100, Timeout.Infinite);
}
catch
{
// ignored
}
}
/// <inheritdoc />
protected override ValueTask WriteBytesToConnection(byte[] bytes, int length)
{
return WriteBytesToConnectionReal(bytes, length);
}
private async ValueTask WriteBytesToConnectionReal(byte[] bytes, int length)
{
try
{
await _socket.SendAsync(bytes, length);
}
catch (NullReferenceException) { }
catch (ObjectDisposedException)
{
// Already disposed and disconnected...
}
catch (SocketException ex)
{
await DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
}
}
/// <inheritdoc />
public override async ValueTask ConnectAsync(byte[] bytes = null)
{
State = ConnectionState.Connecting;
try
{
_socket.Connect(RemoteEndPoint);
}
catch (SocketException e)
{
State = ConnectionState.NotConnected;
throw new HazelException("A SocketException occurred while binding to the port.", e);
}
try
{
_listenTask = ListenAsync();
}
catch (ObjectDisposedException)
{
// If the socket's been disposed then we can just end there but make sure we're in NotConnected state.
// If we end up here I'm really lost...
State = ConnectionState.NotConnected;
return;
}
catch (SocketException e)
{
Dispose();
throw new HazelException("A SocketException occurred while initiating a receive operation.", e);
}
// Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present)
// When acknowledged set the state to connected
await SendHello(bytes, () =>
{
State = ConnectionState.Connected;
InitializeKeepAliveTimer();
});
await _connectWaitLock.WaitAsync(TimeSpan.FromSeconds(10));
}
private async Task ListenAsync()
{
// Start packet handler.
await StartAsync();
// Listen.
while (State != ConnectionState.NotConnected)
{
UdpReceiveResult data;
try
{
data = await _socket.ReceiveAsync();
}
catch (SocketException e)
{
await DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception while reading data: " + e.Message);
return;
}
catch (Exception)
{
return;
}
if (data.Buffer.Length == 0)
{
await DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
return;
}
await HandleAsync(data.Buffer);
}
}
private async ValueTask HandleAsync(ReadOnlyMemory<byte> memory)
{
// Rent memory.
var dest = _pool.Rent(memory.Length);
// Copy data.
memory.CopyTo(dest.Memory);
try
{
// Write to client.
await Pipeline.Writer.WriteAsync(new MessageData(dest, memory.Length));
}
catch (ChannelClosedException)
{
// Clean up.
dest.Dispose();
}
}
protected override void SetState(ConnectionState state)
{
if (state == ConnectionState.Connected)
{
_connectWaitLock.Release();
}
}
/// <summary>
/// Sends a disconnect message to the end point.
/// You may include optional disconnect data. The SendOption must be unreliable.
/// </summary>
protected override async ValueTask<bool> SendDisconnect(MessageWriter data = null)
{
lock (this)
{
if (_state == ConnectionState.NotConnected) return false;
_state = ConnectionState.NotConnected;
}
var bytes = EmptyDisconnectBytes;
if (data != null && data.Length > 0)
{
if (data.SendOption != MessageType.Unreliable)
{
throw new ArgumentException("Disconnect messages can only be unreliable.");
}
bytes = data.ToByteArray(true);
bytes[0] = (byte)UdpSendOption.Disconnect;
}
try
{
await _socket.SendAsync(bytes, bytes.Length, RemoteEndPoint);
}
catch { }
return true;
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
State = ConnectionState.NotConnected;
try { _socket.Close(); } catch { }
try { _socket.Dispose(); } catch { }
_reliablePacketTimer.Dispose();
_connectWaitLock.Dispose();
base.Dispose(disposing);
}
}
}
+26 -1
View File
@@ -187,7 +187,10 @@ namespace Hazel.Udp
_isFirst = false;
// Slice 4 bytes to get handshake data.
await _listener.InvokeNewConnection(message.Slice(4), this);
if (_listener != null)
{
await _listener.InvokeNewConnection(message.Slice(4), this);
}
}
switch (message.Buffer.Span[0])
@@ -256,6 +259,28 @@ namespace Hazel.Udp
Statistics.LogUnreliableSend(length, bytes.Length);
}
/// <summary>
/// Sends a hello packet to the remote endpoint.
/// </summary>
/// <param name="bytes"></param>
/// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param>
protected ValueTask SendHello(byte[] bytes, Action acknowledgeCallback)
{
//First byte of handshake is version indicator so add data after
byte[] actualBytes;
if (bytes == null)
{
actualBytes = new byte[1];
}
else
{
actualBytes = new byte[bytes.Length + 1];
Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length);
}
return HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
+4 -13
View File
@@ -47,16 +47,7 @@ namespace Hazel.Udp
/// <remarks>
/// This will always throw a HazelException.
/// </remarks>
public override void Connect(byte[] bytes = null, int timeout = 5000)
{
throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
}
/// <inheritdoc />
/// <remarks>
/// This will always throw a HazelException.
/// </remarks>
public override void ConnectAsync(byte[] bytes = null)
public override ValueTask ConnectAsync(byte[] bytes = null)
{
throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
}
@@ -64,11 +55,11 @@ namespace Hazel.Udp
/// <summary>
/// Sends a disconnect message to the end point.
/// </summary>
protected override bool SendDisconnect(MessageWriter data = null)
protected override ValueTask<bool> SendDisconnect(MessageWriter data = null)
{
lock (this)
{
if (this._state != ConnectionState.Connected) return false;
if (this._state != ConnectionState.Connected) return ValueTask.FromResult(false);
this._state = ConnectionState.NotConnected;
}
@@ -87,7 +78,7 @@ namespace Hazel.Udp
}
catch { }
return true;
return ValueTask.FromResult(true);
}
protected override void Dispose(bool disposing)
@@ -51,7 +51,7 @@ namespace Impostor.Server.Net.Manager
}
_nodeLocator.Save(gameCodeStr, _publicIp);
_logger.LogDebug("Created game with code {0} ({1}).", game.Code, gameCode);
_logger.LogDebug("Created game with code {0}.", game.Code);
await _eventManager.CallAsync(new GameCreatedEvent(game));
+24
View File
@@ -27,6 +27,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Debugger",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Hazel", "Impostor.Hazel\Impostor.Hazel.csproj", "{671B753B-31AE-4C36-AD71-09CF00FA17CA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{9F1919B0-915B-4749-9944-697DF7E7F67F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client", "Impostor.Client\Impostor.Client.csproj", "{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client.App", "Impostor.Client.App\Impostor.Client.App.csproj", "{3DF86F12-7099-44F6-B98B-A148213D60B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -103,6 +109,22 @@ Global
{671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|Any CPU.Build.0 = Release|Any CPU
{671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|x86.ActiveCfg = Release|Any CPU
{671B753B-31AE-4C36-AD71-09CF00FA17CA}.Release|x86.Build.0 = Release|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|x86.ActiveCfg = Debug|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Debug|x86.Build.0 = Debug|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|Any CPU.Build.0 = Release|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|x86.ActiveCfg = Release|Any CPU
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB}.Release|x86.Build.0 = Release|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|x86.ActiveCfg = Debug|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Debug|x86.Build.0 = Debug|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|Any CPU.Build.0 = Release|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.ActiveCfg = Release|Any CPU
{3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -116,5 +138,7 @@ Global
{7C3EB599-2292-4532-B280-D5BED1094DD4} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
{82B36C4C-4EBD-49B7-A2DB-0B3308FBEF69} = {94FAED42-15BB-40CF-BE64-5D5C3B4ABC8F}
{ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
{BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
{3DF86F12-7099-44F6-B98B-A148213D60B1} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
EndGlobalSection
EndGlobal