connection code for client
This commit is contained in:
parent
7b809a2c60
commit
3738f802c5
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ UserSettings
|
||||
*.csproj
|
||||
*.sln
|
||||
*.DotSettings
|
||||
*.user
|
||||
.idea/
|
||||
# some random downloaded assets
|
||||
Assets/imported/
|
||||
|
||||
@ -27,7 +27,7 @@ namespace FastArena
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
Destroy(this.gameObject);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,37 +2,105 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using FastArena.Network.Packets;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace FastArena
|
||||
namespace FastArena.Network
|
||||
{
|
||||
public enum ClientStatus
|
||||
{
|
||||
Initialized, Connected, Disconnected
|
||||
}
|
||||
|
||||
public class ServerConnection
|
||||
{
|
||||
public IPAddress Ip;
|
||||
public short PortFirst;
|
||||
public Guid SessionId;
|
||||
public byte[] ServerKey;
|
||||
}
|
||||
|
||||
public static class Client
|
||||
{
|
||||
private static TcpClient _tcp;
|
||||
private static UdpClient _udp;
|
||||
private static byte[] _clientKey;
|
||||
private static ServerConnection _serverConnection;
|
||||
|
||||
public static ClientStatus Status { get; private set; } = ClientStatus.Initialized;
|
||||
|
||||
public static void SendUserAction()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void Connect(IPAddress ip, short portFirst)
|
||||
{
|
||||
_tcp = new TcpClient();
|
||||
_udp = new UdpClient();
|
||||
_serverConnection = new ServerConnection()
|
||||
{
|
||||
Ip = ip, PortFirst = portFirst
|
||||
};
|
||||
_udp.Connect(ip, portFirst + 1);
|
||||
_tcp.BeginConnect(ip, portFirst, TCPConnectCallback, null);
|
||||
_udp.BeginSend(StructBinaryConverter.GetBytes(new ConnectionRequestUDP()))
|
||||
}
|
||||
|
||||
private static void ReadExactly(this TcpClient tcp, byte[] buffer, int offset, int exactSize, int timeoutMs = 5000)
|
||||
{
|
||||
int msPassed = 0;
|
||||
while (tcp.Available < exactSize)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
msPassed += 1;
|
||||
if (msPassed > timeoutMs)
|
||||
{
|
||||
throw new TimeoutException(
|
||||
$"couldn't get {exactSize} bytes from socket, {tcp.Available} avaliable");
|
||||
}
|
||||
}
|
||||
|
||||
int readN = _tcp.GetStream().Read(buffer, offset, exactSize);
|
||||
if (readN != exactSize)
|
||||
throw new Exception($"expected to read {exactSize} bytes, got {readN}");
|
||||
}
|
||||
|
||||
private static void TCPConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
_tcp.EndConnect(ar);
|
||||
|
||||
// receive first response from server
|
||||
byte[] buffer = new byte[4096];
|
||||
_tcp.ReadExactly(buffer, 0, Marshal.SizeOf<ConnectionResponseTCP>());
|
||||
//TODO: implement decryption
|
||||
var firstResponse = StructBinaryConverter.ReadStruct<ConnectionResponseTCP>(buffer);
|
||||
firstResponse.ValidateResponseCode();
|
||||
_serverConnection.SessionId = firstResponse.sessionId;
|
||||
_serverConnection.ServerKey = firstResponse.serverKey;
|
||||
|
||||
// connect udp socket associated with current session
|
||||
byte[] byteMessage = StructBinaryConverter.GetBytes(new ConnectionRequestUDP(_serverConnection.SessionId));
|
||||
//TODO: implement encryption
|
||||
_udp.Send(byteMessage, byteMessage.Length);
|
||||
|
||||
// receive second response from server
|
||||
IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.None, 0);
|
||||
byteMessage = _udp.Receive(ref remoteEndpoint);
|
||||
//TODO: implement decryption
|
||||
var secondResponse = StructBinaryConverter.ReadStruct<ConnectionResponseUDP>(byteMessage);
|
||||
secondResponse.ValidateResponseCode();
|
||||
|
||||
//TODO: start tcp read loop
|
||||
//TODO: start udp read loop
|
||||
}
|
||||
|
||||
private static Dictionary<int, SyncTransform> _syncTransforms = new();
|
||||
|
||||
private static void CreateGameObject(int id, ref CreateObjectPacket packet)
|
||||
private static readonly Dictionary<int, SyncTransform> _syncTransforms = new Dictionary<int, SyncTransform>();
|
||||
|
||||
private static void CreateGameObject(ref CreateObjectPacket packet)
|
||||
{
|
||||
var prefab = Resources.Load(packet.prefabPath, typeof(GameObject));
|
||||
var go = (GameObject)Object.Instantiate(prefab);
|
||||
@ -44,9 +112,9 @@ namespace FastArena
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateObjectTransform(int id, ref TransformUpdatePacket packet)
|
||||
private static void UpdateObjectTransform(ref TransformUpdatePacket packet)
|
||||
{
|
||||
if(!_syncTransforms.TryGetValue(id, out var component))
|
||||
if(!_syncTransforms.TryGetValue(packet.gameObjectId, out var component))
|
||||
return;
|
||||
component.UpdateTransform(ref packet);
|
||||
}
|
||||
|
||||
17
Assets/Network/GameObjectId.cs
Normal file
17
Assets/Network/GameObjectId.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastArena.Network
|
||||
{
|
||||
public class GameObjectId : MonoBehaviour
|
||||
{
|
||||
public int Id { get; private set; } = -1;
|
||||
|
||||
internal void _SetId(int packetGameObjectId)
|
||||
{
|
||||
if (Id != -1)
|
||||
throw new Exception($"GameObjectId is already set to {Id}");
|
||||
Id = packetGameObjectId;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Network/GameObjectId.cs.meta
Normal file
3
Assets/Network/GameObjectId.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0274d4d086949a880c5e0b4a9e5cf20
|
||||
timeCreated: 1752248822
|
||||
@ -1,21 +1,75 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastArena
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
internal struct Hash256
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct ConnectionRequestTCP
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
private SHA256 _sha256;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
|
||||
internal readonly byte[] clientKey;
|
||||
|
||||
public ConnectionRequestTCP()
|
||||
public ConnectionRequestTCP(byte[] clientKey)
|
||||
{
|
||||
header = new PacketHeader(PacketType.ConnectionRequestTCP);
|
||||
this.clientKey = clientKey;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct ConnectionResponseTCP
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly StatusCode statusCode;
|
||||
internal readonly Guid sessionId;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
|
||||
internal readonly byte[] serverKey;
|
||||
|
||||
public ConnectionResponseTCP(StatusCode statusCode, Guid sessionId, byte[] serverKey)
|
||||
{
|
||||
header = new PacketHeader(PacketType.ConnectionResponseTCP);
|
||||
this.statusCode = statusCode;
|
||||
this.sessionId = sessionId;
|
||||
this.serverKey = serverKey;
|
||||
}
|
||||
|
||||
public void ValidateResponseCode()
|
||||
{
|
||||
if (statusCode != StatusCode.OK)
|
||||
throw new StatusCodeException(statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct ConnectionRequestUDP
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly Guid sessionId;
|
||||
|
||||
public ConnectionRequestUDP(Guid sessionId)
|
||||
{
|
||||
header = new PacketHeader(PacketType.ConnectionRequestUDP);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct ConnectionResponseUDP
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly StatusCode statusCode;
|
||||
|
||||
public ConnectionResponseUDP(StatusCode statusCode)
|
||||
{
|
||||
header = new PacketHeader(PacketType.ConnectionResponseUDP);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public void ValidateResponseCode()
|
||||
{
|
||||
if (statusCode != StatusCode.OK)
|
||||
throw new StatusCodeException(statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,19 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastArena;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct CreateObjectPacket
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly int gameObjectId;
|
||||
internal readonly string prefabPath;
|
||||
|
||||
internal CreateObjectPacket(int _gameObjectId, string _prefabPath)
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct CreateObjectPacket
|
||||
{
|
||||
header = new PacketHeader(PacketType.CreateObject);
|
||||
gameObjectId = _gameObjectId;
|
||||
prefabPath = _prefabPath;
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly int gameObjectId;
|
||||
internal readonly string prefabPath;
|
||||
|
||||
internal CreateObjectPacket(int _gameObjectId, string _prefabPath)
|
||||
{
|
||||
header = new PacketHeader(PacketType.CreateObject);
|
||||
gameObjectId = _gameObjectId;
|
||||
prefabPath = _prefabPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastArena
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
internal enum PacketType : ushort
|
||||
{
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
namespace FastArena;
|
||||
|
||||
internal static class PacketParser
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
internal static PacketType ReadHeader(byte[] data)
|
||||
internal static class PacketParser
|
||||
{
|
||||
var h = StructBinaryConverter.ReadStruct<PacketHeader>(data);
|
||||
return h.magic == PacketHeader.MAGIC_CONST ? h.type : PacketType.Invalid;
|
||||
}
|
||||
internal static PacketType ReadHeader(byte[] data)
|
||||
{
|
||||
var h = StructBinaryConverter.ReadStruct<PacketHeader>(data);
|
||||
return h.magic == PacketHeader.MAGIC_CONST ? h.type : PacketType.Invalid;
|
||||
}
|
||||
|
||||
internal static T ReadPacket<T>(byte[] data) where T : unmanaged
|
||||
{
|
||||
return StructBinaryConverter.ReadStruct<T>(data);
|
||||
internal static T ReadPacket<T>(byte[] data) where T : unmanaged
|
||||
{
|
||||
return StructBinaryConverter.ReadStruct<T>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Assets/Network/Packets/StructBinaryConverter.cs
Normal file
46
Assets/Network/Packets/StructBinaryConverter.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
public static class StructBinaryConverter
|
||||
{
|
||||
public static byte[] GetBytes<T>(T s) where T : struct
|
||||
{
|
||||
return GetBytes(ref s);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes<T>(ref T s) where T : struct
|
||||
{
|
||||
int size = Marshal.SizeOf<T>();
|
||||
// TODO: implement array pool
|
||||
byte[] buffer = new byte[size];
|
||||
// tel GC to not move the buffer in memory
|
||||
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(s, handle.AddrOfPinnedObject(), false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static T ReadStruct<T>(byte[] buffer) where T : struct
|
||||
{
|
||||
T s;
|
||||
// tel GC to not move the buffer in memory
|
||||
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
s = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastArena;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct TransformUpdatePacket
|
||||
namespace FastArena.Network.Packets
|
||||
{
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly int gameObjectId;
|
||||
internal readonly Vector3 position;
|
||||
internal readonly Quaternion rotation;
|
||||
|
||||
internal TransformUpdatePacket(int _gameObjectId, Transform transform)
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct TransformUpdatePacket
|
||||
{
|
||||
header = new PacketHeader(PacketType.TransformUpdate);
|
||||
gameObjectId = _gameObjectId;
|
||||
position = transform.position;
|
||||
rotation = transform.rotation;
|
||||
internal readonly PacketHeader header;
|
||||
internal readonly int gameObjectId;
|
||||
internal readonly Vector3 position;
|
||||
internal readonly Quaternion rotation;
|
||||
|
||||
internal TransformUpdatePacket(int _gameObjectId, Transform transform)
|
||||
{
|
||||
header = new PacketHeader(PacketType.TransformUpdate);
|
||||
gameObjectId = _gameObjectId;
|
||||
position = transform.position;
|
||||
rotation = transform.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using FastArena.Network.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastArena
|
||||
namespace FastArena.Network
|
||||
{
|
||||
public class ServerConfig
|
||||
{
|
||||
@ -33,7 +34,7 @@ namespace FastArena
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ClientConnection> connections = new List<ClientConnection>();
|
||||
public static readonly List<ClientConnection> Connections = new List<ClientConnection>();
|
||||
|
||||
public static void Start(ServerConfig config)
|
||||
{
|
||||
@ -49,7 +50,7 @@ namespace FastArena
|
||||
{
|
||||
var tcpClient = _tcp.EndAcceptTcpClient(ar);
|
||||
var connection = new ClientConnection(tcpClient);
|
||||
connections.Add(connection);
|
||||
Connections.Add(connection);
|
||||
}
|
||||
|
||||
private static void UdpReceiveCallback(IAsyncResult ar)
|
||||
@ -83,7 +84,7 @@ namespace FastArena
|
||||
public static void SendTransformUpdate(int id, Transform transform)
|
||||
{
|
||||
byte[] data = StructBinaryConverter.GetBytes(new TransformUpdatePacket(id, transform));
|
||||
foreach(var p in connections)
|
||||
foreach(var p in Connections)
|
||||
{
|
||||
UdpSendTo(data, p);
|
||||
}
|
||||
|
||||
22
Assets/Network/StatusCode.cs
Normal file
22
Assets/Network/StatusCode.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace FastArena.Network
|
||||
{
|
||||
public enum StatusCode
|
||||
{
|
||||
OK,
|
||||
ServerError,
|
||||
AccessNotAllowed
|
||||
}
|
||||
|
||||
public class StatusCodeException : Exception
|
||||
{
|
||||
public StatusCode Code;
|
||||
|
||||
public StatusCodeException(StatusCode code) :
|
||||
base($"Status code {(int)code} ({code})")
|
||||
{
|
||||
Code = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Network/StatusCode.cs.meta
Normal file
3
Assets/Network/StatusCode.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ef3bf843e27438cbaae508f73654ec6
|
||||
timeCreated: 1752246290
|
||||
@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FastArena
|
||||
{
|
||||
public static class StructBinaryConverter
|
||||
{
|
||||
public static byte[] GetBytes<T>(T s) where T : unmanaged
|
||||
{
|
||||
return GetBytes(ref s);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes<T>(ref T s) where T : unmanaged
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T));
|
||||
byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(Marshal.SizeOf<T>());
|
||||
|
||||
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (T* s_ptr = &s)
|
||||
{
|
||||
Marshal.Copy((IntPtr)s_ptr, buffer, 0, size);
|
||||
Marshal.StructureToPtr();
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(msg_struct, handle.AddrOfPinnedObject(), false);
|
||||
return buffer;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static T ReadStruct<T>(byte[] buffer) where T : unmanaged
|
||||
{
|
||||
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
T s = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
|
||||
return s;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,8 @@
|
||||
using System;
|
||||
using FastArena.Network.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastArena
|
||||
namespace FastArena.Network
|
||||
{
|
||||
public class GameObjectId : MonoBehaviour
|
||||
{
|
||||
public int Id { get; private set; } = -1;
|
||||
|
||||
internal void _SetId(int packetGameObjectId)
|
||||
{
|
||||
if (Id != -1)
|
||||
throw new Exception($"GameObjectId is already set to {Id}");
|
||||
Id = packetGameObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component fetching GameObject's Transform from the server every tick.
|
||||
/// </summary>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"com.unity.ide.rider": "3.0.28",
|
||||
"com.unity.ide.visualstudio": "2.0.22",
|
||||
"com.unity.ide.rider": "3.0.36",
|
||||
"com.unity.ide.visualstudio": "2.0.23",
|
||||
"com.unity.ide.vscode": "1.2.5",
|
||||
"com.unity.textmeshpro": "2.1.6",
|
||||
"com.unity.ugui": "1.0.0",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.rider": {
|
||||
"version": "3.0.28",
|
||||
"version": "3.0.36",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
@ -17,7 +17,7 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.visualstudio": {
|
||||
"version": "2.0.22",
|
||||
"version": "2.0.23",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user