BITKit/Src/Core/Net/NetProvider.cs

508 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 该接口为基础的网络接口,包括了网络服务,服务端接口,客户端接口的基本定义 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);
}
}
}