508 lines
18 KiB
C#
508 lines
18 KiB
C#
/*
|
||
* 该接口为基础的网络接口,包括了网络服务,服务端接口,客户端接口的基本定义 e.g.
|
||
* ⭐INetProvider 网络通信接口的基本定义
|
||
* ⭐INetServer 服务端接口的基本定义
|
||
* ⭐INetClient 客户端接口的基本定义
|
||
*/
|
||
|
||
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using Cysharp.Threading.Tasks;
|
||
using kcp2k;
|
||
using Microsoft.CodeAnalysis.Scripting;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
|
||
|
||
namespace BITKit
|
||
{
|
||
/// <summary>
|
||
/// 帮助类
|
||
/// </summary>
|
||
public static class NetUtils
|
||
{
|
||
/// <summary>
|
||
/// 计算文件大小函数(保留两位小数),Size为字节大小
|
||
/// </summary>
|
||
/// <param name="size">初始文件大小</param>
|
||
/// <returns></returns>
|
||
public static string GetFileSize(long size)
|
||
{
|
||
var num = 1024.00; //byte
|
||
|
||
|
||
if (size < num)
|
||
return size + "B";
|
||
if (size < Math.Pow(num, 2))
|
||
return (size / num).ToString("f2") + "K"; //kb
|
||
if (size < Math.Pow(num, 3))
|
||
return (size / Math.Pow(num, 2)).ToString("f2") + "M"; //M
|
||
if (size < Math.Pow(num, 4))
|
||
return (size / Math.Pow(num, 3)).ToString("f2") + "G"; //G
|
||
|
||
|
||
return (size / Math.Pow(num, 4)).ToString("f2") + "T"; //T
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 网络指令类型
|
||
/// </summary>
|
||
public enum NetCommandType:byte
|
||
{
|
||
Undefined=0,
|
||
Message=1,
|
||
Heartbeat=2,
|
||
Rpc,
|
||
SetPropertyValue,
|
||
SetFieldValue,
|
||
WaitTask,
|
||
ReturnValue,
|
||
}
|
||
/// <summary>
|
||
/// 网络提供服务,包括了基础网络服务,e.g
|
||
/// ⭐向服务器发送指令
|
||
/// ⭐向所有客户端发送指令
|
||
/// ⭐向单个客户端发送指令
|
||
/// ⭐监听与取消监听网络命令
|
||
/// ⭐从服务器获取数据
|
||
/// ⭐从客户端获取数据
|
||
/// ⭐添加Rpc处理服务
|
||
/// ⭐向服务器发送Rpc
|
||
/// ⭐向所有客户端发送Rpc
|
||
/// </summary>
|
||
public interface INetProvider
|
||
{
|
||
|
||
public uint TickRate { get; set; }
|
||
|
||
/// <summary>
|
||
/// 更新Tick
|
||
/// </summary>
|
||
void Tick();
|
||
/// <summary>
|
||
/// 连接协议握手
|
||
/// </summary>
|
||
void HandShake();
|
||
/// <summary>
|
||
/// 获取远程接口
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <returns></returns>
|
||
T GetRemoteInterface<T>();
|
||
/// <summary>
|
||
/// 远程调用
|
||
/// </summary>
|
||
void Invoke(Memory<byte> bytes);
|
||
/// <summary>
|
||
/// 异步远程调用,有返回结果
|
||
/// </summary>
|
||
/// <param name="bytes"></param>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <returns></returns>
|
||
UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 服务端接口,支持服务端的基本功能
|
||
/// ⭐开启服务器(支持指定端口)
|
||
/// ⭐停止服务器
|
||
/// ⭐服务器运行状态
|
||
/// </summary>
|
||
public interface INetServer
|
||
{
|
||
/// <summary>
|
||
/// 通信接口
|
||
/// </summary>
|
||
public INetProvider NetProvider=>this as INetProvider;
|
||
/// <summary>
|
||
/// 源物体,用于通过代理直接访问
|
||
/// </summary>
|
||
public object Source => this;
|
||
/// <summary>
|
||
/// 手动Tick
|
||
/// </summary>
|
||
public bool ManualTick { get; set; }
|
||
/// <summary>
|
||
/// 回调:当客户端连接时
|
||
/// </summary>
|
||
public event Action<int> OnClientConnected;
|
||
|
||
/// <summary>
|
||
/// 回调:当客户端断开连接时
|
||
/// </summary>
|
||
public event Action<int> OnClientDisconnected;
|
||
/// <summary>
|
||
/// 开启服务端的回调
|
||
/// </summary>
|
||
public event Action OnStartServer;
|
||
/// <summary>
|
||
/// 关闭服务端的回调
|
||
/// </summary>
|
||
public event Action OnStopServer;
|
||
|
||
/// <summary>
|
||
/// 运行服务端
|
||
/// </summary>
|
||
/// <param name="port">端口,默认为27014</param>
|
||
void StartServer(ushort port = 27014);
|
||
|
||
/// <summary>
|
||
/// 停止服务端
|
||
/// </summary>
|
||
/// <param name="dispose">可选参数:强行释放,默认为false</param>
|
||
void StopServer(bool dispose=false);
|
||
|
||
/// <summary>
|
||
/// (只读)服务器是否正在运行
|
||
/// </summary>
|
||
bool IsRunningServer { get; }
|
||
|
||
/// <summary>
|
||
/// 向单个链接发送消息
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <param name="message"></param>
|
||
void SendMessageToClient(int id, string message);
|
||
/// <summary>
|
||
/// 向全部链接发送消息
|
||
/// </summary>
|
||
/// <param name="message"></param>
|
||
void SendMessageToAll(string message);
|
||
/// <summary>
|
||
/// 所有已连接的客户端
|
||
/// </summary>
|
||
public IDictionary<int,EndPoint> Connections { get; }
|
||
|
||
/// <summary>
|
||
/// 踢出客户端
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
void KickClient(int id);
|
||
}
|
||
/// <summary>
|
||
/// 基本网络客户端的接口定义,包括了基本客户端的功能
|
||
/// ⭐基本客户端回调
|
||
/// ⭐是否已连接到服务器
|
||
/// ⭐连接服务端的延迟
|
||
/// ⭐客户端Id
|
||
/// ⭐开启客户端
|
||
/// ⭐关闭客户端
|
||
/// </summary>
|
||
public interface INetClient
|
||
{
|
||
/// <summary>
|
||
/// 通讯接口
|
||
/// </summary>
|
||
public INetProvider NetProvider=>this as INetProvider;
|
||
|
||
//基本客户端回调
|
||
public event Action OnStartConnect;
|
||
public event Action OnConnected;
|
||
public event Action OnDisconnected;
|
||
public event Action OnConnectedFailed;
|
||
/// <summary>
|
||
/// 是否已连接到服务端
|
||
/// </summary>
|
||
bool IsConnected { get; }
|
||
/// <summary>
|
||
/// 手动Tick
|
||
/// </summary>
|
||
bool ManualTick { get; set; }
|
||
/// <summary>
|
||
/// 连接服务端的延迟
|
||
/// </summary>
|
||
int Ping { get; }
|
||
/// <summary>
|
||
/// 客户端Id
|
||
/// </summary>
|
||
int Id { get; }
|
||
/// <summary>
|
||
/// 断开链接
|
||
/// </summary>
|
||
void Disconnect();
|
||
/// <summary>
|
||
/// 异步开始链接
|
||
/// </summary>
|
||
/// <param name="address">服务端地址</param>
|
||
/// <param name="port">端口</param>
|
||
/// <returns></returns>
|
||
UniTask<bool> Connect(string address = "127.0.0.1", ushort port = 27014);
|
||
|
||
/// <summary>
|
||
/// 向服务端发送消息
|
||
/// </summary>
|
||
/// <param name="message">消息</param>
|
||
void SendServerMessage(string message);
|
||
}
|
||
|
||
public class NetProviderCommon:INetProvider
|
||
{
|
||
public static readonly BITSharp.ICodeGenerator CodeGenerator = new RemoteInterfaceGenerator();
|
||
|
||
private class RemoteInterfaceGenerator:BITSharp.CodeGenerator
|
||
{
|
||
public INetProvider NetProvider = new NetProviderCommon();
|
||
|
||
public override string BeforeGenerate(Type type)
|
||
{
|
||
return $"public {nameof(INetProvider)} {nameof(NetProvider)};";
|
||
}
|
||
|
||
public override IReadOnlyList<string> GenerateNamespaces(Type type)
|
||
{
|
||
return new string[]
|
||
{
|
||
"System.IO",
|
||
typeof(INetProvider).Namespace,
|
||
};
|
||
}
|
||
|
||
public override string GenerateMethodContext(MethodInfo methodInfo)
|
||
{
|
||
var codeBuilder = new StringBuilder();
|
||
var parameterInfos = methodInfo.GetParameters();
|
||
|
||
using var ms = new MemoryStream();
|
||
using var writer = new BinaryWriter(ms);
|
||
|
||
foreach (var parameterInfo in methodInfo.GetParameters())
|
||
{
|
||
if (parameterInfo.IsOut)
|
||
{
|
||
codeBuilder.AppendLine($"{parameterInfo.Name} = default;");
|
||
}
|
||
}
|
||
|
||
codeBuilder.AppendLine(" using var ms = new MemoryStream();");
|
||
codeBuilder.AppendLine(" using var writer = new BinaryWriter(ms);");
|
||
codeBuilder.AppendLine(" writer.Write((byte)NetCommandType.Rpc);");
|
||
codeBuilder.AppendLine(" writer.Write((byte)NetCommandType.Rpc);");
|
||
|
||
codeBuilder.AppendLine($" writer.Write(\"{methodInfo.DeclaringType!.FullName}\");");
|
||
codeBuilder.AppendLine($" writer.Write(\"{methodInfo.Name}\");");
|
||
|
||
codeBuilder.AppendLine($" writer.Write({parameterInfos.Length});");
|
||
|
||
foreach (var parameterInfo in parameterInfos)
|
||
{
|
||
codeBuilder.AppendLine($"BITBinary.Write(writer,{parameterInfo.Name});");
|
||
}
|
||
|
||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||
|
||
if (isAwaitable)
|
||
{
|
||
var generics = methodInfo.ReturnType.CSharpName();
|
||
|
||
generics= generics.Replace(nameof(UniTask), string.Empty);
|
||
generics = generics.Replace(nameof(Task), string.Empty);
|
||
|
||
codeBuilder.AppendLine($" return BITBinary.Read{generics}((await NetProvider.InvokeAsync(ms.ToArray())).ToArray());");
|
||
}
|
||
else
|
||
{
|
||
codeBuilder.AppendLine(base.GenerateMethodContext(methodInfo));
|
||
}
|
||
|
||
|
||
return codeBuilder.ToString();
|
||
}
|
||
|
||
public override string GenerateProperty(PropertyInfo propertyInfo)
|
||
{
|
||
var codeBuilder = new StringBuilder();
|
||
|
||
var source = base.GenerateProperty(propertyInfo);
|
||
|
||
source = source.Replace("get;", $"\nget=>_{propertyInfo.Name};");
|
||
source = source.Replace("set;", $"\nset{{\n_{propertyInfo.Name}=value; \n using var ms = new MemoryStream();\n using var writer = new BinaryWriter(ms);\n writer.Write((byte)NetCommandType.Rpc);\n writer.Write((byte)NetCommandType.SetPropertyValue);\n writer.Write(\"{propertyInfo.DeclaringType!.FullName}\");\n writer.Write(\"{propertyInfo.Name}\");\n BITBinary.Write(writer,value);\n NetProvider.Invoke(ms.ToArray());\n }}");
|
||
|
||
codeBuilder.AppendLine(source);
|
||
codeBuilder.AppendLine($"public {propertyInfo.PropertyType.CSharpName()} _{propertyInfo.Name};");
|
||
|
||
return codeBuilder.ToString();
|
||
}
|
||
}
|
||
private void SyntaxTest()
|
||
{
|
||
/*
|
||
using var ms = new MemoryStream();
|
||
using var writer = new BinaryWriter(ms);
|
||
writer.Write((byte)NetCommandType.Rpc);
|
||
writer.Write((byte)NetCommandType.SetPropertyValue);
|
||
writer.Write(nameof(SyntaxTest));
|
||
BITBinary.Write(writer,1);
|
||
NetProvider.Invoke(ms.ToArray());
|
||
*/
|
||
}
|
||
|
||
public IServiceProvider ServiceProvider { get; set; } = new ServiceCollection().BuildServiceProvider();
|
||
|
||
public readonly GenericEvent Events = new();
|
||
|
||
public int Timeout=8000;
|
||
|
||
public int Index;
|
||
|
||
public readonly ConcurrentDictionary<int, UniTaskCompletionSource<Memory<byte>>> P2P = new();
|
||
public readonly ConcurrentDictionary<int,DateTime> LastHeartbeat = new();
|
||
public readonly ConcurrentDictionary<string, object> RemoteInterfaces = new();
|
||
|
||
public readonly ConcurrentDictionary<Type,object> LocalServices = new();
|
||
|
||
public readonly ConcurrentQueue<(int id,byte[] bytes)> SendQueue = new();
|
||
public readonly ConcurrentDictionary<int,int> DropCount = new();
|
||
|
||
public readonly byte[] HeartBeat = new byte[] { (byte)NetCommandType.Heartbeat };
|
||
|
||
public uint TickRate { get; set; }
|
||
public void Tick()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
public void HandShake()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
public async void OnDataInternal(int id,ArraySegment<byte> bytes, KcpChannel channel)
|
||
{
|
||
using var ms = new MemoryStream(bytes.ToArray());
|
||
using var reader = new BinaryReader(ms);
|
||
|
||
var type = (NetCommandType)ms.ReadByte();
|
||
|
||
var now = DateTime.Now;
|
||
|
||
switch (type)
|
||
{
|
||
case NetCommandType.Heartbeat:
|
||
{
|
||
LastHeartbeat.GetOrCreate(id);
|
||
LastHeartbeat[id]=now;
|
||
}
|
||
break;
|
||
case NetCommandType.Message:
|
||
{
|
||
var message = reader.ReadString();
|
||
BIT4Log.Log<INetProvider>(message);
|
||
}
|
||
break;
|
||
case NetCommandType.Rpc:
|
||
{
|
||
var command = (NetCommandType)ms.ReadByte();
|
||
|
||
var serviceName = reader.ReadString();
|
||
var serviceType = BITSharp.GetTypeFromFullName(serviceName);
|
||
var service = ServiceProvider.GetRequiredService(serviceType);
|
||
|
||
switch (command)
|
||
{
|
||
case NetCommandType.SetPropertyValue:
|
||
{
|
||
serviceType.GetProperty(reader.ReadString())!.SetValue(service,BITBinary.Read(reader));
|
||
}
|
||
break;
|
||
case NetCommandType.SetFieldValue:
|
||
{
|
||
serviceType.GetField(reader.ReadString())!.SetValue(service,BITBinary.Read(reader));
|
||
}
|
||
break;
|
||
case NetCommandType.Rpc:
|
||
{
|
||
var methodInfo = serviceType.GetMethod(reader.ReadString())!;
|
||
var parameterLength = reader.ReadInt32();
|
||
var parameters = new object[parameterLength];
|
||
for (var i = 0; i < parameterLength; i++)
|
||
{
|
||
parameters[i] = BITBinary.Read(reader);
|
||
}
|
||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||
if (isAwaitable)
|
||
{
|
||
dynamic result = methodInfo.Invoke(service, parameters)!;
|
||
|
||
object resultValue = null;
|
||
|
||
if (methodInfo.ReturnType == typeof(void)
|
||
||
|
||
methodInfo.ReturnType == typeof(UniTask)
|
||
||
|
||
methodInfo.ReturnType == typeof(UniTask<>)
|
||
)
|
||
{
|
||
await result;
|
||
resultValue = -1;
|
||
}
|
||
else
|
||
{
|
||
resultValue = await result;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
methodInfo.Invoke(service, parameters);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
public T GetRemoteInterface<T>()
|
||
{
|
||
if(RemoteInterfaces.TryGetValue(typeof(T).FullName!,out var obj))
|
||
{
|
||
return (T)obj;
|
||
}
|
||
|
||
var code = CodeGenerator.Generate(typeof(T));
|
||
|
||
var options = ScriptOptions.Default;
|
||
|
||
var assemblies = BITSharp.GetReferencedAssemblies(typeof(T));
|
||
|
||
foreach (var referencedAssembly in assemblies)
|
||
{
|
||
options= options.AddReferences(referencedAssembly);
|
||
}
|
||
options = options.AddReferences(typeof(BITApp).Assembly);
|
||
|
||
var assembly = BITSharp.Compile(code, options);
|
||
|
||
var type = assembly.GetExportedTypes().First(x => typeof(T).IsAssignableFrom(x));
|
||
|
||
var instance = Activator.CreateInstance(type)!;
|
||
|
||
instance.GetType().GetField("NetProvider")!.SetValue(instance,this);
|
||
|
||
return (T)instance;
|
||
}
|
||
|
||
public void Invoke(Memory<byte> bytes)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
public async UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes)
|
||
{
|
||
var index = Index++;
|
||
var source = new UniTaskCompletionSource<Memory<byte>>();
|
||
P2P.TryAdd(index, source);
|
||
|
||
var cancelCts = new CancellationTokenSource();
|
||
cancelCts.CancelAfter(Timeout);
|
||
|
||
return await source.Task.AttachExternalCancellation(cancelCts.Token);
|
||
}
|
||
}
|
||
}
|