This commit is contained in:
CortexCore 2025-07-05 17:19:53 +08:00
commit e8dcb21a3e
10 changed files with 1328 additions and 0 deletions

19
.gitattributes vendored Normal file
View File

@ -0,0 +1,19 @@
# Git - gitattributes Documentation
# https://git-scm.com/docs/gitattributes
# 禁用所有文件的换行符自动转换
* -text
# 机器生成的文件
# Unity *.meta 所有平台下换行符均为 LF
*.meta text eol=lf
# 人类编写的文件
# C# 代码
*.cs text eol=lf

575
.gitignore vendored Normal file
View File

@ -0,0 +1,575 @@
# ---> Unity
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Recordings can get excessive in size
/[Rr]ecordings/
# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*
# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Visual Studio cache directory
.vs/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.aab
*.unitypackage
*.app
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> VisualStudio
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# custom
.idea
.plastic
.vscode
/Assets/StreamingAssets/yoo
yoo/
Bundles/
Profiler/

128
Program.cs Normal file
View File

@ -0,0 +1,128 @@
// See https://aka.ms/new-console-template for more information
using System.Diagnostics;
using System.Globalization;
using System.Text;
using BITKit;
using BITKit.Mod;
using BITKit.Net;
using MemoryPack;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Net.BITKit.Teleport;
await BITAppForNet.InitializeAsync("Net.BITKit.Teleport");
var isClient = Environment.GetCommandLineArgs().Contains("client", StringComparer.CurrentCultureIgnoreCase);
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(x =>
{
x.AddConsole();
Console.OutputEncoding=Encoding.UTF8;
});
serviceCollection.AddTransient<NetProviderService>();
serviceCollection.AddSingleton<ENetServer>();
serviceCollection.AddSingleton<ENetClient>();
if (isClient)
{
serviceCollection.AddSingleton<INetProvider>(x => x.GetRequiredService<ENetClient>());
serviceCollection.AddRemoteInterface<IMyRemoteInterface>();
}
else
{
serviceCollection.AddSingleton<INetProvider>(x => x.GetRequiredService<ENetServer>());
serviceCollection.AddSingleton<IMyRemoteInterface,MyRemoteInterface>();
}
var serviceProvider = serviceCollection.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
if (isClient)
{
var client = serviceProvider.GetRequiredService<ENetClient>();
Tick(client);
if (await client.Connect() is false)
{
throw new Exception("Failed to connect to server.");
}
var rpc = serviceProvider.GetRequiredService<IMyRemoteInterface>();
for (int i = 1; i <= 3; i++)
{
//rpc.Func(i.ToString());
logger.LogInformation("Rpc:\t" + await rpc.FuncAsync(i.ToString()));
}
logger.LogInformation("客户端链接完成按任意键开始Rpc测试");
var isPressed=false;
Console.CancelKeyPress += (sender, args) =>
{
isPressed = true;
args.Cancel = true; // Prevent the process from terminating
};
while (true)
{
if (isPressed)
{
using var stopWatcher = new StopWatcher(logger,"Rpc测试");
logger.LogInformation("Rpc:\t" + await rpc.FuncAsync(DateTime.Now.ToString(CultureInfo.InvariantCulture)));
isPressed = false;
}
await Task.Delay(100);
}
}
else
{
var server = serviceProvider.GetRequiredService<ENetServer>();
Tick(server);
server.StartServer();
var count = 0;
while (true)
{
await Task.Delay(1000);
foreach (var id in server.Connections.Keys)
{
server.SendMessageToClient(id,$"Tick:{++count} from server!");
}
}
}
while (true)
{
await Task.Delay(300);
}
async void Tick(INetProvider netProvider)
{
while (true)
{
netProvider.Tick();
await Task.Delay(30);
}
}

252
Src/ENetClient.cs Normal file
View 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
View 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
View 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
View 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
View 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}!");
}
}
}

View 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
View 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": {}
}