1
This commit is contained in:
252
Src/ENetClient.cs
Normal file
252
Src/ENetClient.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ENet;
|
||||
using BITKit;
|
||||
using BITKit.Net;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Net.BITKit.Teleport;
|
||||
|
||||
public class ENetClient : IDisposable, INetClient,INetProvider
|
||||
{
|
||||
|
||||
private readonly NetProviderService _netProviderService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<ENetClient> _logger;
|
||||
|
||||
// ========== 事件 ==========
|
||||
public event Action? OnStartConnect;
|
||||
public event Action? OnConnected;
|
||||
public event Action? OnDisconnected;
|
||||
public event Action? OnConnectedFailed;
|
||||
public event Action<byte[], int>? OnData;
|
||||
|
||||
// ========== 属性 ==========
|
||||
public bool IsConnected => _isConnected.Allow && _peer is { IsSet: true };
|
||||
public bool IsConnection => _isConnection.Allow;
|
||||
public bool ManualTick { get; set; }
|
||||
public int Id { get; private set; }
|
||||
public int Ping { get; private set; }
|
||||
|
||||
// ========== 字段 ==========
|
||||
private Host _client;
|
||||
private Peer _peer;
|
||||
private readonly ValidHandle _manualConnect = new();
|
||||
private readonly ValidHandle _isConnected = new();
|
||||
|
||||
private readonly ValidHandle _manualConnected = new();
|
||||
|
||||
private readonly ValidHandle _isConnection = new();
|
||||
|
||||
// ========== 构造 ==========
|
||||
public ENetClient(ILogger<ENetClient> logger, IServiceProvider serviceProvider, NetProviderService netProviderService)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
_netProviderService = netProviderService;
|
||||
ENet.Library.Initialize();
|
||||
|
||||
_isConnected.AddListener(connected =>
|
||||
{
|
||||
if (connected)
|
||||
OnConnected?.Invoke();
|
||||
else
|
||||
OnConnectedFailed?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 主动连接 ==========
|
||||
public async UniTask<bool> Connect(string address = "127.0.0.1", ushort port = 27014)
|
||||
{
|
||||
if (IsConnected) return true;
|
||||
|
||||
await _isConnection;
|
||||
|
||||
using var connecting = _isConnection.GetHandle();
|
||||
|
||||
|
||||
_manualConnect.AddElement(0);
|
||||
_client = new Host();
|
||||
|
||||
var eNetAddress = new Address();
|
||||
eNetAddress.SetHost(address);
|
||||
eNetAddress.Port = port;
|
||||
|
||||
_client.Create();
|
||||
_peer = _client.Connect(eNetAddress);
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
if (!_manualConnect.Allow)
|
||||
return false;
|
||||
|
||||
if (_isConnected)
|
||||
break;
|
||||
|
||||
_client.Flush();
|
||||
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
|
||||
if (!_isConnected) return false;
|
||||
|
||||
_manualConnected.AddElement(0);
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ========== 主动断开 ==========
|
||||
public void Disconnect()
|
||||
{
|
||||
_manualConnect.RemoveElement(0);
|
||||
}
|
||||
|
||||
// ========== 主动轮询网络事件 ==========
|
||||
public int RpcCount { get; set; }
|
||||
public uint TickRate { get; set; }
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
_netProviderService.Tick();
|
||||
|
||||
if(_client is null )return;
|
||||
|
||||
_client.Flush();
|
||||
|
||||
{
|
||||
var packet = (Packet)default;
|
||||
|
||||
packet.Create(NetProviderService.Heartbeat, PacketFlags.Reliable);
|
||||
|
||||
_peer.Send(0, ref packet);
|
||||
|
||||
packet.Dispose();
|
||||
}
|
||||
|
||||
while (_client.CheckEvents(out var netEvent) > 0 || _client.Service(0, out netEvent) > 0)
|
||||
{
|
||||
Id = (int)(netEvent.Peer.ID - ENetServer.Offset);
|
||||
|
||||
switch (netEvent.Type)
|
||||
{
|
||||
case EventType.None:
|
||||
break;
|
||||
|
||||
case EventType.Connect:
|
||||
// 假设你已经连接成功并拿到 peer
|
||||
netEvent.Peer.Timeout(3, 1000, 2000);
|
||||
|
||||
_isConnected.AddElement(0);
|
||||
_logger.LogInformation("✅ Client connected to server.");
|
||||
break;
|
||||
|
||||
case EventType.Disconnect:
|
||||
_isConnected.RemoveElement(0);
|
||||
_logger.LogInformation("❌ Client disconnected from server.");
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
|
||||
case EventType.Timeout:
|
||||
_isConnected.RemoveElement(0);
|
||||
_logger.LogInformation("⏰ Client connection timeout.");
|
||||
OnDisconnected?.Invoke();
|
||||
break;
|
||||
|
||||
case EventType.Receive:
|
||||
|
||||
|
||||
_isConnected.AddElement(0);
|
||||
var buffer = new byte[netEvent.Packet.Length];
|
||||
netEvent.Packet.CopyTo(buffer);
|
||||
|
||||
|
||||
OnData?.Invoke(buffer, netEvent.ChannelID);
|
||||
|
||||
try
|
||||
{
|
||||
_netProviderService.OnData(Id, buffer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogCritical(e, e.Message);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
netEvent.Packet.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void HandShake()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public T GetRemoteInterface<T>() => _netProviderService.GetRemoteInterface<T>();
|
||||
|
||||
public void Invoke(int id, byte[] bytes)
|
||||
{
|
||||
if (IsConnected is false)
|
||||
{
|
||||
throw new NetOfflineException();
|
||||
}
|
||||
|
||||
var packet = default(Packet);
|
||||
packet.Create(bytes);
|
||||
_peer.Send(0,ref packet);
|
||||
|
||||
packet.Dispose();
|
||||
}
|
||||
|
||||
public async UniTask<byte[]> InvokeAsync(int id, byte[] bytes)
|
||||
{
|
||||
var rpcCount = RpcCount;
|
||||
|
||||
Invoke(id,bytes);
|
||||
|
||||
var task = _netProviderService.TaskCompletionSources[rpcCount] = new UniTaskCompletionSource<byte[]>();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
|
||||
try
|
||||
{
|
||||
bytes = await task.Task.AttachExternalCancellation(timeout.Token);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
await UniTask.SwitchToMainThread();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========== 占位方法 ==========
|
||||
public void SendServerMessage(string message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// ========== 释放 ==========
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_client?.Flush();
|
||||
_client?.Dispose();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
10
Src/ENetCommon.cs
Normal file
10
Src/ENetCommon.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using BITKit;
|
||||
|
||||
namespace Net.BITKit.Teleport
|
||||
{
|
||||
public class ENetCommon
|
||||
{
|
||||
public static readonly byte[] Heartbeat = {(byte)NetCommandType.Heartbeat};
|
||||
}
|
||||
}
|
257
Src/ENetServer.cs
Normal file
257
Src/ENetServer.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using BITKit;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using ENet;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Net.BITKit.Teleport
|
||||
{
|
||||
|
||||
|
||||
public class ENetServer : IDisposable, INetServer, INetProvider
|
||||
{
|
||||
public const uint Offset = 2147483648;
|
||||
|
||||
private readonly NetProviderService _netProviderService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<ENetServer> _logger;
|
||||
|
||||
// ========== 事件 ==========
|
||||
public event Action<int>? OnClientConnected;
|
||||
public event Action<int>? OnClientDisconnected;
|
||||
public event Action? OnStartServer;
|
||||
public event Action? OnStopServer;
|
||||
public event Action<byte[], int>? OnData;
|
||||
|
||||
private readonly Dictionary<uint, Queue<byte[]>> _packetQueue = new();
|
||||
private readonly Dictionary<uint, IntervalUpdate> _packetLossInterval = new();
|
||||
|
||||
// ========== 属性 ==========
|
||||
public bool IsRunningServer => _server is { IsSet: true };
|
||||
|
||||
public bool ManualTick
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDictionary<int, EndPoint> Connections { get; } = new Dictionary<int, EndPoint>();
|
||||
|
||||
// ========== 字段 ==========
|
||||
private Host? _server;
|
||||
|
||||
// ========== 启动/关闭 ==========
|
||||
|
||||
public ENetServer(ILogger<ENetServer> logger, IServiceProvider serviceProvider,
|
||||
NetProviderService netProviderService)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
_netProviderService = netProviderService;
|
||||
ENet.Library.Initialize();
|
||||
}
|
||||
|
||||
public void StartServer(ushort port = 27014)
|
||||
{
|
||||
if (_server is not null) return;
|
||||
|
||||
_server = new Host();
|
||||
var address = new Address { Port = port };
|
||||
|
||||
_server.Create(address, 64);
|
||||
OnStartServer?.Invoke();
|
||||
_logger.LogInformation($"✅ Server started on port {port}");
|
||||
}
|
||||
|
||||
public void StopServer(bool dispose = false)
|
||||
{
|
||||
if (_server is null) return;
|
||||
|
||||
_server.Flush();
|
||||
_server.Dispose();
|
||||
_server = null;
|
||||
|
||||
OnStopServer?.Invoke();
|
||||
_logger.LogInformation("🛑 Server stopped.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopServer();
|
||||
}
|
||||
|
||||
// ========== 主动轮询 ==========
|
||||
public int RpcCount { get; set; }
|
||||
public uint TickRate { get; set; } = 64;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (_server is not { IsSet: true }) return;
|
||||
{
|
||||
var packet = (Packet)default;
|
||||
packet.Create(NetProviderService.Heartbeat, PacketFlags.Reliable);
|
||||
_server.Broadcast(0, ref packet);
|
||||
|
||||
packet.Dispose();
|
||||
}
|
||||
|
||||
_netProviderService.Tick();
|
||||
|
||||
while (_server.CheckEvents(out var netEvent) > 0 || _server.Service(0, out netEvent) > 0)
|
||||
{
|
||||
var peerId = (int)(netEvent.Peer.ID - Offset);
|
||||
|
||||
if (_packetQueue.TryGetValue(netEvent.Peer.ID, out var queue))
|
||||
{
|
||||
_packetLossInterval.GetOrCreate(netEvent.Peer.ID).Reset();
|
||||
|
||||
while (queue.TryDequeue(out var bytes))
|
||||
{
|
||||
var packet = (Packet)default;
|
||||
|
||||
packet.Create(bytes);
|
||||
|
||||
netEvent.Peer.Send(0, ref packet);
|
||||
|
||||
packet.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
switch (netEvent.Type)
|
||||
{
|
||||
case EventType.Connect:
|
||||
|
||||
netEvent.Peer.Timeout(3, 1000, 2000);
|
||||
|
||||
Connections[peerId] = new IPEndPoint(IPAddress.Parse(netEvent.Peer.IP), netEvent.Peer.Port);
|
||||
_logger.LogInformation(
|
||||
$"✅ Client connected - ID: {peerId},UID:{netEvent.Peer.ID}, IP: {netEvent.Peer.IP}");
|
||||
OnClientConnected?.Invoke(peerId);
|
||||
|
||||
/*
|
||||
var heartbeatPacket = default(Packet);
|
||||
heartbeatPacket.Create(ENetCommon.Heartbeat,PacketFlags.Reliable);
|
||||
|
||||
netEvent.Peer.Send(0, ref heartbeatPacket);
|
||||
heartbeatPacket.Dispose();
|
||||
*/
|
||||
|
||||
break;
|
||||
|
||||
case EventType.Disconnect:
|
||||
Connections.Remove(peerId);
|
||||
_logger.LogInformation(
|
||||
$"❌ Client disconnected - ID: {peerId},UID:{netEvent.Peer.ID},IP: {netEvent.Peer.IP}");
|
||||
OnClientDisconnected?.Invoke(peerId);
|
||||
break;
|
||||
|
||||
case EventType.Timeout:
|
||||
Connections.Remove(peerId);
|
||||
_logger.LogWarning($"⏰ Client timeout - ID: {peerId}, IP: {netEvent.Peer.IP}");
|
||||
OnClientDisconnected?.Invoke(peerId);
|
||||
break;
|
||||
|
||||
case EventType.Receive:
|
||||
{
|
||||
var buffer = new byte[netEvent.Packet.Length];
|
||||
netEvent.Packet.CopyTo(buffer);
|
||||
|
||||
OnData?.Invoke(buffer, netEvent.ChannelID);
|
||||
|
||||
if (netEvent.ChannelID is 0)
|
||||
{
|
||||
_netProviderService.OnData(peerId, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
netEvent.Packet.Dispose();
|
||||
}
|
||||
|
||||
foreach (var (id, queue) in _packetQueue)
|
||||
{
|
||||
var packetLossInterval = _packetLossInterval.GetOrCreate(id);
|
||||
if (queue.Count > 0 && packetLossInterval.AllowUpdateWithoutReset)
|
||||
{
|
||||
_logger.LogWarning($"{id}丢包x{queue.Count}");
|
||||
queue.Clear();
|
||||
packetLossInterval.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
_server.Flush();
|
||||
}
|
||||
|
||||
public void HandShake()
|
||||
{
|
||||
_logger.LogInformation("client called HandShake");
|
||||
}
|
||||
|
||||
public T GetRemoteInterface<T>() => _netProviderService.GetRemoteInterface<T>();
|
||||
|
||||
public void Invoke(int id, byte[] bytes)
|
||||
{
|
||||
var uid = (uint)(id + Offset);
|
||||
|
||||
_packetQueue.GetOrCreate(uid).Enqueue(bytes);
|
||||
}
|
||||
|
||||
|
||||
public async UniTask<byte[]> InvokeAsync(int id, byte[] bytes)
|
||||
{
|
||||
Invoke(id, bytes);
|
||||
|
||||
var task = _netProviderService.TaskCompletionSources[RpcCount] = new UniTaskCompletionSource<byte[]>();
|
||||
|
||||
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
|
||||
return await task.Task.AttachExternalCancellation(timeout.Token);
|
||||
}
|
||||
|
||||
// ========== 消息发送 ==========
|
||||
public void SendMessageToClient(int id, string message)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var bw = new BinaryWriter(ms);
|
||||
|
||||
bw.Write((byte)NetCommandType.Message);
|
||||
bw.Write(message);
|
||||
|
||||
Invoke(id, ms.ToArray());
|
||||
}
|
||||
|
||||
public void SendMessageToAll(string message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// ========== 踢人 ==========
|
||||
public void KickClient(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void TestFunc()
|
||||
{
|
||||
_logger.LogInformation("TestFunc called");
|
||||
}
|
||||
|
||||
public void TestFunc1(bool x)
|
||||
{
|
||||
_logger.LogInformation($"TestFunc1 called with parameter: {x}");
|
||||
}
|
||||
|
||||
public UniTask<bool> TestFuncAsync(bool x)
|
||||
{
|
||||
_logger.LogInformation($"TestFuncAsync called with parameter: {x}");
|
||||
return UniTask.FromResult(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
37
Src/GenClass.cs
Normal file
37
Src/GenClass.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Net.BITKit.Teleport;
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BITKit;
|
||||
using MemoryPack;
|
||||
public class @IMyRemoteInterfaceGen : IMyRemoteInterface
|
||||
{
|
||||
public INetProvider NetProvider;
|
||||
public String Func( String name) { using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Rpc);
|
||||
writer.Write(++NetProvider.RpcCount);
|
||||
writer.Write("Net.BITKit.Teleport.IMyRemoteInterface");
|
||||
writer.Write("Func");
|
||||
RemoteInterfaceGenerator.Serialize(writer,name);
|
||||
NetProvider.Invoke(0,ms.ToArray());
|
||||
return default;
|
||||
|
||||
}
|
||||
|
||||
public async UniTask<String> FuncAsync( String name) { using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Rpc);
|
||||
writer.Write(++NetProvider.RpcCount);
|
||||
writer.Write("Net.BITKit.Teleport.IMyRemoteInterface");
|
||||
writer.Write("FuncAsync");
|
||||
RemoteInterfaceGenerator.Serialize(writer,name);
|
||||
var bytes =await NetProvider.InvokeAsync(0, ms.ToArray());
|
||||
return MemoryPackSerializer.Deserialize<String>(bytes);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
23
Src/MyRemoteInterface.cs
Normal file
23
Src/MyRemoteInterface.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace Net.BITKit.Teleport
|
||||
{
|
||||
public interface IMyRemoteInterface
|
||||
{
|
||||
public string Func(string name);
|
||||
public UniTask<string> FuncAsync(string name);
|
||||
}
|
||||
|
||||
public class MyRemoteInterface : IMyRemoteInterface
|
||||
{
|
||||
public string Func(string name)
|
||||
{
|
||||
return $"Hello, {name}!";
|
||||
}
|
||||
|
||||
public UniTask<string> FuncAsync(string name)
|
||||
{
|
||||
return UniTask.FromResult($"Hello, {name}!");
|
||||
}
|
||||
}
|
||||
}
|
17
Src/Net.BITKit.ENet.asmdef
Normal file
17
Src/Net.BITKit.ENet.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Net.BITKit.ENet",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
10
Src/package.json
Normal file
10
Src/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "com.bitkit.enet",
|
||||
"displayName": "ENet-CSharp Support",
|
||||
"version": "2024.3.31",
|
||||
"unity": "2022.3",
|
||||
"description": "ENet-CSharp Support for BITKit",
|
||||
"keywords": [ "BITKit", "Framework","dotnet",".net","ENet" ],
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
}
|
Reference in New Issue
Block a user