257 lines
8.0 KiB
C#
257 lines
8.0 KiB
C#
|
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);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|