@@ -0,0 +1,141 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Platform.Net;
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供操作地址映射的辅助方法。
|
||||
/// </summary>
|
||||
public static class AddressableHelper
|
||||
{
|
||||
// 声明一个私有静态只读列表 AddressableScenes,用于存储地址映射的场景配置信息
|
||||
private static readonly List<AddressableScene> AddressableScenes = new List<AddressableScene>();
|
||||
|
||||
static AddressableHelper()
|
||||
{
|
||||
// 遍历场景配置信息,筛选出地址映射类型的场景,并添加到 AddressableScenes 列表中
|
||||
foreach (var sceneConfig in SceneConfigData.Instance.List)
|
||||
{
|
||||
if (sceneConfig.SceneTypeString == "Addressable")
|
||||
{
|
||||
AddressableScenes.Add(new AddressableScene(sceneConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加地址映射并返回操作结果。
|
||||
/// </summary>
|
||||
/// <param name="scene">场景实例。</param>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <param name="routeId">路由 ID。</param>
|
||||
/// <param name="isLock">是否锁定。</param>
|
||||
public static async FTask AddAddressable(Scene scene, long addressableId, long routeId, bool isLock = true)
|
||||
{
|
||||
// 获取指定索引的地址映射场景配置信息
|
||||
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
|
||||
// 调用内部路由方法,发送添加地址映射的请求并等待响应
|
||||
var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId,
|
||||
new I_AddressableAdd_Request
|
||||
{
|
||||
AddressableId = addressableId, RouteId = routeId, IsLock = isLock
|
||||
});
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
Log.Error($"AddAddressable error is {response.ErrorCode}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取地址映射的路由 ID。
|
||||
/// </summary>
|
||||
/// <param name="scene">场景实例。</param>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <returns>地址映射的路由 ID。</returns>
|
||||
public static async FTask<long> GetAddressableRouteId(Scene scene, long addressableId)
|
||||
{
|
||||
// 获取指定索引的地址映射场景配置信息
|
||||
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
|
||||
// 调用内部路由方法,发送获取地址映射路由 ID 的请求并等待响应
|
||||
var response = (I_AddressableGet_Response) await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId,
|
||||
new I_AddressableGet_Request
|
||||
{
|
||||
AddressableId = addressableId
|
||||
});
|
||||
// 检查响应错误码,如果为零,返回路由 ID;否则,输出错误信息并返回 0
|
||||
if (response.ErrorCode == 0)
|
||||
{
|
||||
return response.RouteId;
|
||||
}
|
||||
|
||||
Log.Error($"GetAddressable error is {response.ErrorCode} addressableId:{addressableId}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定地址映射。
|
||||
/// </summary>
|
||||
/// <param name="scene">场景实例。</param>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
public static async FTask RemoveAddressable(Scene scene, long addressableId)
|
||||
{
|
||||
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
|
||||
var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId,
|
||||
new I_AddressableRemove_Request
|
||||
{
|
||||
AddressableId = addressableId
|
||||
});
|
||||
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
Log.Error($"RemoveAddressable error is {response.ErrorCode}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定指定地址映射。
|
||||
/// </summary>
|
||||
/// <param name="scene">场景实例。</param>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
public static async FTask LockAddressable(Scene scene, long addressableId)
|
||||
{
|
||||
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
|
||||
var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId,
|
||||
new I_AddressableLock_Request
|
||||
{
|
||||
AddressableId = addressableId
|
||||
});
|
||||
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
Log.Error($"LockAddressable error is {response.ErrorCode}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解锁指定地址映射。
|
||||
/// </summary>
|
||||
/// <param name="scene">场景实例。</param>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <param name="routeId">路由 ID。</param>
|
||||
/// <param name="source">解锁来源。</param>
|
||||
public static async FTask UnLockAddressable(Scene scene, long addressableId, long routeId, string source)
|
||||
{
|
||||
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
|
||||
var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId,
|
||||
new I_AddressableUnLock_Request
|
||||
{
|
||||
AddressableId = addressableId,
|
||||
RouteId = routeId,
|
||||
Source = source
|
||||
});
|
||||
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
Log.Error($"UnLockAddressable error is {response.ErrorCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
#if FANTASY_NET
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
public class AddressableManageComponentAwakeSystem : AwakeSystem<AddressableManageComponent>
|
||||
{
|
||||
protected override void Awake(AddressableManageComponent self)
|
||||
{
|
||||
self.AddressableLock = self.Scene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64());
|
||||
}
|
||||
}
|
||||
|
||||
public class AddressableManageComponentDestroySystem : DestroySystem<AddressableManageComponent>
|
||||
{
|
||||
protected override void Destroy(AddressableManageComponent self)
|
||||
{
|
||||
foreach (var (_, waitCoroutineLock) in self.Locks)
|
||||
{
|
||||
waitCoroutineLock.Dispose();
|
||||
}
|
||||
|
||||
self.Locks.Clear();
|
||||
self.Addressable.Clear();
|
||||
self.AddressableLock.Dispose();
|
||||
self.AddressableLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AddressableManageComponent : Entity
|
||||
{
|
||||
public CoroutineLock AddressableLock;
|
||||
public readonly Dictionary<long, long> Addressable = new();
|
||||
public readonly Dictionary<long, WaitCoroutineLock> Locks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 添加地址映射。
|
||||
/// </summary>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <param name="routeId">路由 ID。</param>
|
||||
/// <param name="isLock">是否进行锁定。</param>
|
||||
public async FTask Add(long addressableId, long routeId, bool isLock)
|
||||
{
|
||||
WaitCoroutineLock waitCoroutineLock = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (isLock)
|
||||
{
|
||||
waitCoroutineLock = await AddressableLock.Wait(addressableId);
|
||||
}
|
||||
|
||||
Addressable[addressableId] = routeId;
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"AddressableManageComponent Add addressableId:{addressableId} routeId:{routeId}");
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
waitCoroutineLock?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取地址映射的路由 ID。
|
||||
/// </summary>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <returns>地址映射的路由 ID。</returns>
|
||||
public async FTask<long> Get(long addressableId)
|
||||
{
|
||||
using (await AddressableLock.Wait(addressableId))
|
||||
{
|
||||
Addressable.TryGetValue(addressableId, out var routeId);
|
||||
return routeId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除地址映射。
|
||||
/// </summary>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
public async FTask Remove(long addressableId)
|
||||
{
|
||||
using (await AddressableLock.Wait(addressableId))
|
||||
{
|
||||
Addressable.Remove(addressableId);
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"Addressable Remove addressableId: {addressableId} _addressable:{Addressable.Count}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定地址映射。
|
||||
/// </summary>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
public async FTask Lock(long addressableId)
|
||||
{
|
||||
var waitCoroutineLock = await AddressableLock.Wait(addressableId);
|
||||
Locks.Add(addressableId, waitCoroutineLock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解锁地址映射。
|
||||
/// </summary>
|
||||
/// <param name="addressableId">地址映射的唯一标识。</param>
|
||||
/// <param name="routeId">新的路由 ID。</param>
|
||||
/// <param name="source">解锁来源。</param>
|
||||
public void UnLock(long addressableId, long routeId, string source)
|
||||
{
|
||||
if (!Locks.Remove(addressableId, out var coroutineLock))
|
||||
{
|
||||
Log.Error($"Addressable unlock not found addressableId: {addressableId} Source:{source}");
|
||||
return;
|
||||
}
|
||||
|
||||
Addressable.TryGetValue(addressableId, out var oldAddressableId);
|
||||
|
||||
if (routeId != 0)
|
||||
{
|
||||
Addressable[addressableId] = routeId;
|
||||
}
|
||||
|
||||
coroutineLock.Dispose();
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"Addressable UnLock key: {addressableId} oldAddressableId : {oldAddressableId} routeId: {routeId} Source:{source}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
public class AddressableMessageComponentDestroySystem : DestroySystem<AddressableMessageComponent>
|
||||
{
|
||||
protected override void Destroy(AddressableMessageComponent self)
|
||||
{
|
||||
if (self.AddressableId != 0)
|
||||
{
|
||||
AddressableHelper.RemoveAddressable(self.Scene, self.AddressableId).Coroutine();
|
||||
self.AddressableId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可寻址消息组件、挂载了这个组件可以接收Addressable消息
|
||||
/// </summary>
|
||||
public sealed class AddressableMessageComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 可寻址消息组件的唯一标识。
|
||||
/// </summary>
|
||||
public long AddressableId;
|
||||
|
||||
/// <summary>
|
||||
/// 注册可寻址消息组件。
|
||||
/// </summary>
|
||||
/// <param name="isLock">是否进行锁定。</param>
|
||||
public FTask Register(bool isLock = true)
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
throw new Exception("AddressableRouteComponent must be mounted under a component");
|
||||
}
|
||||
|
||||
AddressableId = Parent.Id;
|
||||
|
||||
if (AddressableId == 0)
|
||||
{
|
||||
throw new Exception("AddressableRouteComponent.Parent.Id is null");
|
||||
}
|
||||
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"AddressableMessageComponent Register addressableId:{AddressableId} RouteId:{Parent.RouteId}");
|
||||
#endif
|
||||
return AddressableHelper.AddAddressable(Scene, AddressableId, Parent.RouteId, isLock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定可寻址消息组件。
|
||||
/// </summary>
|
||||
public FTask Lock()
|
||||
{
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"AddressableMessageComponent Lock {Parent.Id}");
|
||||
#endif
|
||||
return AddressableHelper.LockAddressable(Scene, Parent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解锁可寻址消息组件。
|
||||
/// </summary>
|
||||
/// <param name="source">解锁来源。</param>
|
||||
public FTask UnLock(string source)
|
||||
{
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"AddressableMessageComponent UnLock {Parent.Id} {Parent.RouteId}");
|
||||
#endif
|
||||
return AddressableHelper.UnLockAddressable(Scene, Parent.Id, Parent.RouteId, source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定可寻址消息并且释放掉AddressableMessageComponent组件。
|
||||
/// 该方法不会自动取Addressable中心删除自己的信息。
|
||||
/// 用于传送或转移到其他服务器时使用
|
||||
/// </summary>
|
||||
public async FTask LockAndRelease()
|
||||
{
|
||||
await Lock();
|
||||
AddressableId = 0;
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,218 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Scheduler;
|
||||
using Fantasy.Timer;
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
public class AddressableRouteComponentAwakeSystem : AwakeSystem<AddressableRouteComponent>
|
||||
{
|
||||
protected override void Awake(AddressableRouteComponent self)
|
||||
{
|
||||
((Session)self.Parent).AddressableRouteComponent = self;
|
||||
|
||||
var selfScene = self.Scene;
|
||||
self.TimerComponent = selfScene.TimerComponent;
|
||||
self.NetworkMessagingComponent = selfScene.NetworkMessagingComponent;
|
||||
self.MessageDispatcherComponent = selfScene.MessageDispatcherComponent;
|
||||
self.AddressableRouteLock =
|
||||
selfScene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64());
|
||||
}
|
||||
}
|
||||
|
||||
public class AddressableRouteComponentDestroySystem : DestroySystem<AddressableRouteComponent>
|
||||
{
|
||||
protected override void Destroy(AddressableRouteComponent self)
|
||||
{
|
||||
self.AddressableRouteLock.Dispose();
|
||||
|
||||
self.AddressableRouteId = 0;
|
||||
self.AddressableId = 0;
|
||||
self.TimerComponent = null;
|
||||
self.AddressableRouteLock = null;
|
||||
self.NetworkMessagingComponent = null;
|
||||
self.MessageDispatcherComponent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可寻址路由消息组件,挂载了这个组件可以接收和发送 Addressable 消息。
|
||||
/// </summary>
|
||||
public sealed class AddressableRouteComponent : Entity
|
||||
{
|
||||
public long AddressableId;
|
||||
public long AddressableRouteId;
|
||||
public CoroutineLock AddressableRouteLock;
|
||||
public TimerComponent TimerComponent;
|
||||
public NetworkMessagingComponent NetworkMessagingComponent;
|
||||
public MessageDispatcherComponent MessageDispatcherComponent;
|
||||
|
||||
internal void Send(IAddressableRouteMessage message)
|
||||
{
|
||||
Call(message).Coroutine();
|
||||
}
|
||||
|
||||
internal async FTask Send(Type requestType, APackInfo packInfo)
|
||||
{
|
||||
await Call(requestType, packInfo);
|
||||
}
|
||||
|
||||
internal async FTask<IResponse> Call(Type requestType, APackInfo packInfo)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoute);
|
||||
}
|
||||
|
||||
packInfo.IsDisposed = true;
|
||||
var failCount = 0;
|
||||
var runtimeId = RuntimeId;
|
||||
IResponse iRouteResponse = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (await AddressableRouteLock.Wait(AddressableId, "AddressableRouteComponent Call MemoryStream"))
|
||||
{
|
||||
while (!IsDisposed)
|
||||
{
|
||||
if (AddressableRouteId == 0)
|
||||
{
|
||||
AddressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, AddressableId);
|
||||
}
|
||||
|
||||
if (AddressableRouteId == 0)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(requestType,
|
||||
InnerErrorCode.ErrNotFoundRoute);
|
||||
}
|
||||
|
||||
iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, requestType, packInfo);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout;
|
||||
}
|
||||
|
||||
switch (iRouteResponse.ErrorCode)
|
||||
{
|
||||
case InnerErrorCode.ErrRouteTimeout:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
case InnerErrorCode.ErrNotFoundRoute:
|
||||
{
|
||||
if (++failCount > 20)
|
||||
{
|
||||
Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
await TimerComponent.Net.WaitAsync(100);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout;
|
||||
}
|
||||
|
||||
AddressableRouteId = 0;
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
|
||||
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用可寻址路由消息并等待响应。
|
||||
/// </summary>
|
||||
/// <param name="request">可寻址路由请求。</param>
|
||||
private async FTask<IResponse> Call(IAddressableRouteMessage request)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute);
|
||||
}
|
||||
|
||||
var failCount = 0;
|
||||
var runtimeId = RuntimeId;
|
||||
|
||||
using (await AddressableRouteLock.Wait(AddressableId, "AddressableRouteComponent Call"))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (AddressableRouteId == 0)
|
||||
{
|
||||
AddressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, AddressableId);
|
||||
}
|
||||
|
||||
if (AddressableRouteId == 0)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(request.GetType(),
|
||||
InnerErrorCode.ErrNotFoundRoute);
|
||||
}
|
||||
|
||||
var iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, request);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout;
|
||||
}
|
||||
|
||||
switch (iRouteResponse.ErrorCode)
|
||||
{
|
||||
case InnerErrorCode.ErrNotFoundRoute:
|
||||
{
|
||||
if (++failCount > 20)
|
||||
{
|
||||
Log.Error(
|
||||
$"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
await TimerComponent.Net.WaitAsync(500);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout;
|
||||
}
|
||||
|
||||
AddressableRouteId = 0;
|
||||
continue;
|
||||
}
|
||||
case InnerErrorCode.ErrRouteTimeout:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.IdFactory;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// AddressableScene
|
||||
/// </summary>
|
||||
public sealed class AddressableScene
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public readonly long Id;
|
||||
/// <summary>
|
||||
/// RunTimeId
|
||||
/// </summary>
|
||||
public readonly long RunTimeId;
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="sceneConfig">sceneConfig</param>
|
||||
public AddressableScene(SceneConfig sceneConfig)
|
||||
{
|
||||
Id = IdFactoryHelper.EntityId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0);
|
||||
RunTimeId = IdFactoryHelper.RuntimeId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 声明一个 sealed 类 I_AddressableAddHandler,继承自 RouteRPC 类,并指定泛型参数
|
||||
/// </summary>
|
||||
public sealed class I_AddressableAddHandler : RouteRPC<Scene, I_AddressableAdd_Request, I_AddressableAdd_Response>
|
||||
{
|
||||
/// <summary>
|
||||
/// 在收到地址映射添加请求时执行的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前场景实例。</param>
|
||||
/// <param name="request">包含请求信息的 I_AddressableAdd_Request 实例。</param>
|
||||
/// <param name="response">用于构建响应的 I_AddressableAdd_Response 实例。</param>
|
||||
/// <param name="reply">执行响应的回调操作。</param>
|
||||
protected override async FTask Run(Scene scene, I_AddressableAdd_Request request, I_AddressableAdd_Response response, Action reply)
|
||||
{
|
||||
await scene.GetComponent<AddressableManageComponent>().Add(request.AddressableId, request.RouteId, request.IsLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 声明一个 sealed 类 I_AddressableGetHandler,继承自 RouteRPC 类,并指定泛型参数
|
||||
/// </summary>
|
||||
public sealed class I_AddressableGetHandler : RouteRPC<Scene, I_AddressableGet_Request, I_AddressableGet_Response>
|
||||
{
|
||||
/// <summary>
|
||||
/// 在收到地址映射获取请求时执行的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前场景实例。</param>
|
||||
/// <param name="request">包含请求信息的 I_AddressableGet_Request 实例。</param>
|
||||
/// <param name="response">用于构建响应的 I_AddressableGet_Response 实例。</param>
|
||||
/// <param name="reply">执行响应的回调操作。</param>
|
||||
protected override async FTask Run(Scene scene, I_AddressableGet_Request request, I_AddressableGet_Response response, Action reply)
|
||||
{
|
||||
response.RouteId = await scene.GetComponent<AddressableManageComponent>().Get(request.AddressableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 声明一个 sealed 类 I_AddressableLockHandler,继承自 RouteRPC 类,并指定泛型参数
|
||||
/// </summary>
|
||||
public sealed class I_AddressableLockHandler : RouteRPC<Scene, I_AddressableLock_Request, I_AddressableLock_Response>
|
||||
{
|
||||
/// <summary>
|
||||
/// 在收到地址映射锁定请求时执行的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前场景实例。</param>
|
||||
/// <param name="request">包含请求信息的 I_AddressableLock_Request 实例。</param>
|
||||
/// <param name="response">用于构建响应的 I_AddressableLock_Response 实例。</param>
|
||||
/// <param name="reply">执行响应的回调操作。</param>
|
||||
protected override async FTask Run(Scene scene, I_AddressableLock_Request request, I_AddressableLock_Response response, Action reply)
|
||||
{
|
||||
await scene.GetComponent<AddressableManageComponent>().Lock(request.AddressableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 声明一个 sealed 类 I_AddressableRemoveHandler,继承自 RouteRPC 类,并指定泛型参数
|
||||
/// </summary>
|
||||
public sealed class I_AddressableRemoveHandler : RouteRPC<Scene, I_AddressableRemove_Request, I_AddressableRemove_Response>
|
||||
{
|
||||
/// <summary>
|
||||
/// 在收到地址映射移除请求时执行的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前场景实例。</param>
|
||||
/// <param name="request">包含请求信息的 I_AddressableRemove_Request 实例。</param>
|
||||
/// <param name="response">用于构建响应的 I_AddressableRemove_Response 实例。</param>
|
||||
/// <param name="reply">执行响应的回调操作。</param>
|
||||
protected override async FTask Run(Scene scene, I_AddressableRemove_Request request, I_AddressableRemove_Response response, Action reply)
|
||||
{
|
||||
await scene.GetComponent<AddressableManageComponent>().Remove(request.AddressableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Route
|
||||
{
|
||||
/// <summary>
|
||||
/// 声明一个 sealed 类 I_AddressableUnLockHandler,继承自 RouteRPC 类,并指定泛型参数
|
||||
/// </summary>
|
||||
public sealed class I_AddressableUnLockHandler : RouteRPC<Scene, I_AddressableUnLock_Request, I_AddressableUnLock_Response>
|
||||
{
|
||||
/// <summary>
|
||||
/// 在收到地址映射解锁请求时执行的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="scene">当前场景实例。</param>
|
||||
/// <param name="request">包含请求信息的 I_AddressableUnLock_Request 实例。</param>
|
||||
/// <param name="response">用于构建响应的 I_AddressableUnLock_Response 实例。</param>
|
||||
/// <param name="reply">执行响应的回调操作。</param>
|
||||
protected override async FTask Run(Scene scene, I_AddressableUnLock_Request request, I_AddressableUnLock_Response response, Action reply)
|
||||
{
|
||||
scene.GetComponent<AddressableManageComponent>().UnLock(request.AddressableId, request.RouteId, request.Source);
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Serialize;
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// MemoryStreamBuffer对象池类
|
||||
/// </summary>
|
||||
public sealed class MemoryStreamBufferPool : IDisposable
|
||||
{
|
||||
private readonly int _poolSize;
|
||||
private readonly int _maxMemoryStreamSize;
|
||||
private readonly Queue<MemoryStreamBuffer> _memoryStreamPool = new Queue<MemoryStreamBuffer>();
|
||||
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="maxMemoryStreamSize"></param>
|
||||
/// <param name="poolSize"></param>
|
||||
public MemoryStreamBufferPool(int maxMemoryStreamSize = 2048, int poolSize = 512)
|
||||
{
|
||||
_poolSize = poolSize;
|
||||
_maxMemoryStreamSize = maxMemoryStreamSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 租借MemoryStream
|
||||
/// </summary>
|
||||
/// <param name="memoryStreamBufferSource"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
|
||||
{
|
||||
if (size > _maxMemoryStreamSize)
|
||||
{
|
||||
return new MemoryStreamBuffer(memoryStreamBufferSource, size);
|
||||
}
|
||||
|
||||
if (size < _maxMemoryStreamSize)
|
||||
{
|
||||
size = _maxMemoryStreamSize;
|
||||
}
|
||||
|
||||
if (_memoryStreamPool.Count == 0)
|
||||
{
|
||||
return new MemoryStreamBuffer(memoryStreamBufferSource, size);
|
||||
}
|
||||
|
||||
if (_memoryStreamPool.TryDequeue(out var memoryStream))
|
||||
{
|
||||
memoryStream.MemoryStreamBufferSource = memoryStreamBufferSource;
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
return new MemoryStreamBuffer(memoryStreamBufferSource, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 归还ReturnMemoryStream
|
||||
/// </summary>
|
||||
/// <param name="memoryStreamBuffer"></param>
|
||||
public void ReturnMemoryStream(MemoryStreamBuffer memoryStreamBuffer)
|
||||
{
|
||||
if (memoryStreamBuffer.Capacity > _maxMemoryStreamSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_memoryStreamPool.Count > _poolSize)
|
||||
{
|
||||
// 设置该值只能是内网或服务器转发的时候可能在连接之前发送的数据过多的情况下可以修改。
|
||||
// 设置过大会导致内存占用过大,所以要谨慎设置。
|
||||
return;
|
||||
}
|
||||
|
||||
memoryStreamBuffer.SetLength(0);
|
||||
memoryStreamBuffer.MemoryStreamBufferSource = MemoryStreamBufferSource.None;
|
||||
_memoryStreamPool.Enqueue(memoryStreamBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁方法
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var memoryStreamBuffer in _memoryStreamPool)
|
||||
{
|
||||
memoryStreamBuffer.MemoryStreamBufferSource = MemoryStreamBufferSource.None;
|
||||
memoryStreamBuffer.Dispose();
|
||||
}
|
||||
_memoryStreamPool.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示消息处理器的接口,处理特定类型的消息。
|
||||
/// </summary>
|
||||
public interface IMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type();
|
||||
/// <summary>
|
||||
/// 处理消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="messageTypeCode">消息类型代码。</param>
|
||||
/// <param name="message">要处理的消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型消息基类,实现了 <see cref="IMessageHandler"/> 接口。
|
||||
/// </summary>
|
||||
public abstract class Message<T> : IMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="messageTypeCode">消息类型代码。</param>
|
||||
/// <param name="message">要处理的消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Run(session, (T) message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行消息处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="message">要处理的消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
protected abstract FTask Run(Session session, T message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型消息RPC基类,实现了 <see cref="IMessageHandler"/> 接口,用于处理请求和响应类型的消息。
|
||||
/// </summary>
|
||||
public abstract class MessageRPC<TRequest, TResponse> : IMessageHandler where TRequest : IRequest where TResponse : AMessage, IResponse, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="messageTypeCode">消息类型代码。</param>
|
||||
/// <param name="message">要处理的消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message)
|
||||
{
|
||||
if (message is not TRequest request)
|
||||
{
|
||||
Log.Error($"消息类型转换错误: {message.GetType().Name} to {typeof(TRequest).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
var response = new TResponse();
|
||||
var isReply = false;
|
||||
|
||||
void Reply()
|
||||
{
|
||||
if (isReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isReply = true;
|
||||
|
||||
if (session.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(session, request, response, Reply);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
response.ErrorCode = InnerErrorCode.ErrRpcFail;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Reply();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行消息处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="request">请求消息。</param>
|
||||
/// <param name="response">响应消息。</param>
|
||||
/// <param name="reply">发送响应的方法。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
protected abstract FTask Run(Session session, TRequest request, TResponse response, Action reply);
|
||||
}
|
||||
#if FANTASY_UNITY
|
||||
public interface IMessageDelegateHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="delegate"></param>
|
||||
public void Register(object @delegate);
|
||||
/// <summary>
|
||||
/// 取消注册消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="delegate"></param>
|
||||
public int UnRegister(object @delegate);
|
||||
/// <summary>
|
||||
/// 处理消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="message"></param>
|
||||
public void Handle(Session session, object message);
|
||||
}
|
||||
public delegate FTask MessageDelegate<in T>(Session session, T msg) where T : IMessage;
|
||||
public sealed class MessageDelegateHandler<T> : IMessageDelegateHandler, IDisposable where T : IMessage
|
||||
{
|
||||
private readonly List<MessageDelegate<T>> _delegates = new List<MessageDelegate<T>>();
|
||||
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(T);
|
||||
}
|
||||
|
||||
public void Register(object @delegate)
|
||||
{
|
||||
var a = (MessageDelegate<T>)@delegate;
|
||||
|
||||
if (_delegates.Contains(a))
|
||||
{
|
||||
Log.Error($"{typeof(T).Name} already register action delegateName:{a.Method.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_delegates.Add(a);
|
||||
}
|
||||
|
||||
public int UnRegister(object @delegate)
|
||||
{
|
||||
_delegates.Remove((MessageDelegate<T>)@delegate);
|
||||
return _delegates.Count;
|
||||
}
|
||||
|
||||
public void Handle(Session session, object message)
|
||||
{
|
||||
foreach (var registerDelegate in _delegates)
|
||||
{
|
||||
try
|
||||
{
|
||||
registerDelegate(session, (T)message).Coroutine();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_delegates.Clear();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#if FANTASY_NET
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示路由消息处理器的接口,处理特定类型的路由消息。
|
||||
/// </summary>
|
||||
public interface IRouteMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type();
|
||||
|
||||
/// <summary>
|
||||
/// 处理路由消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="entity">实体对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">要处理的路由消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型路由基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理特定实体和路由消息类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TMessage">路由消息类型。</typeparam>
|
||||
public abstract class Route<TEntity, TMessage> : IRouteMessageHandler where TEntity : Entity where TMessage : IRouteMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理路由消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="entity">实体对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">要处理的路由消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TMessage ruteMessage)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, ruteMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行路由消息处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象。</param>
|
||||
/// <param name="message">要处理的路由消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
protected abstract FTask Run(TEntity entity, TMessage message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型路由RPC基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理请求和响应类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TRouteRequest">路由请求类型。</typeparam>
|
||||
/// <typeparam name="TRouteResponse">路由响应类型。</typeparam>
|
||||
public abstract class RouteRPC<TEntity, TRouteRequest, TRouteResponse> : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IRouteRequest where TRouteResponse : AMessage, IRouteResponse, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取处理的消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TRouteRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理路由消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象。</param>
|
||||
/// <param name="entity">实体对象。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">要处理的路由消息。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TRouteRequest tRouteRequest)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var isReply = false;
|
||||
var response = new TRouteResponse();
|
||||
|
||||
void Reply()
|
||||
{
|
||||
if (isReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isReply = true;
|
||||
|
||||
if (session.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, tRouteRequest, response, Reply);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
response.ErrorCode = InnerErrorCode.ErrRpcFail;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Reply();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行路由消息处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象。</param>
|
||||
/// <param name="request">请求路由消息。</param>
|
||||
/// <param name="response">响应路由消息。</param>
|
||||
/// <param name="reply">发送响应的方法。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型可寻址路由基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理特定实体和可寻址路由消息类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TMessage">可寻址路由消息类型。</typeparam>
|
||||
public abstract class Addressable<TEntity, TMessage> : IRouteMessageHandler where TEntity : Entity where TMessage : IAddressableRouteMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理可寻址路由消息。
|
||||
/// </summary>
|
||||
/// <param name="session">会话。</param>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">可寻址路由消息。</param>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TMessage ruteMessage)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, ruteMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Send(new RouteResponse(), rpcId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行处理可寻址路由消息。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="message">可寻址路由消息。</param>
|
||||
protected abstract FTask Run(TEntity entity, TMessage message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型可寻址RPC路由基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理特定实体和可寻址RPC路由请求类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TRouteRequest">可寻址RPC路由请求类型。</typeparam>
|
||||
/// <typeparam name="TRouteResponse">可寻址RPC路由响应类型。</typeparam>
|
||||
public abstract class AddressableRPC<TEntity, TRouteRequest, TRouteResponse> : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IAddressableRouteRequest where TRouteResponse : IAddressableRouteResponse, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TRouteRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理可寻址RPC路由请求。
|
||||
/// </summary>
|
||||
/// <param name="session">会话。</param>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">可寻址RPC路由请求。</param>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TRouteRequest tRouteRequest)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var isReply = false;
|
||||
var response = new TRouteResponse();
|
||||
|
||||
void Reply()
|
||||
{
|
||||
if (isReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isReply = true;
|
||||
|
||||
if (session.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, tRouteRequest, response, Reply);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
response.ErrorCode = InnerErrorCode.ErrRpcFail;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Reply();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行处理可寻址RPC路由请求。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="request">可寻址RPC路由请求。</param>
|
||||
/// <param name="response">可寻址RPC路由响应。</param>
|
||||
/// <param name="reply">回复操作。</param>
|
||||
protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型漫游路由基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理特定实体和漫游路由消息类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TMessage">漫游消息类型。</typeparam>
|
||||
public abstract class Roaming<TEntity, TMessage> : IRouteMessageHandler where TEntity : Entity where TMessage : IRoamingMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理漫游消息。
|
||||
/// </summary>
|
||||
/// <param name="session">会话。</param>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">漫游消息。</param>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TMessage ruteMessage)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, ruteMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Send(new RouteResponse(), rpcId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行处理漫游消息。
|
||||
/// </summary>
|
||||
/// <param name="terminus">终点实体。</param>
|
||||
/// <param name="message">漫游消息。</param>
|
||||
protected abstract FTask Run(TEntity terminus, TMessage message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 漫游RPC路由基类,实现了 <see cref="IRouteMessageHandler"/> 接口,用于处理特定实体和漫游RPC路由请求类型的路由。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">实体类型。</typeparam>
|
||||
/// <typeparam name="TRouteRequest">漫游RPC路由请求类型。</typeparam>
|
||||
/// <typeparam name="TRouteResponse">漫游RPC路由响应类型。</typeparam>
|
||||
public abstract class RoamingRPC<TEntity, TRouteRequest, TRouteResponse> : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IRoamingRequest where TRouteResponse : IRoamingResponse, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息类型。
|
||||
/// </summary>
|
||||
/// <returns>消息类型。</returns>
|
||||
public Type Type()
|
||||
{
|
||||
return typeof(TRouteRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理漫游RPC路由请求。
|
||||
/// </summary>
|
||||
/// <param name="session">会话。</param>
|
||||
/// <param name="entity">实体。</param>
|
||||
/// <param name="rpcId">RPC标识。</param>
|
||||
/// <param name="routeMessage">漫游RPC路由请求。</param>
|
||||
public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage)
|
||||
{
|
||||
if (routeMessage is not TRouteRequest tRouteRequest)
|
||||
{
|
||||
Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is not TEntity tEntity)
|
||||
{
|
||||
Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var isReply = false;
|
||||
var response = new TRouteResponse();
|
||||
|
||||
void Reply()
|
||||
{
|
||||
if (isReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isReply = true;
|
||||
|
||||
if (session.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Run(tEntity, tRouteRequest, response, Reply);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (entity is not Scene scene)
|
||||
{
|
||||
scene = entity.Scene;
|
||||
}
|
||||
|
||||
Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
|
||||
response.ErrorCode = InnerErrorCode.ErrRpcFail;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Reply();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行处理漫游RPC路由请求。
|
||||
/// </summary>
|
||||
/// <param name="terminus">终点实体。</param>
|
||||
/// <param name="request">漫游RPC路由请求。</param>
|
||||
/// <param name="response">漫游RPC路由响应。</param>
|
||||
/// <param name="reply">回复操作。</param>
|
||||
protected abstract FTask Run(TEntity terminus, TRouteRequest request, TRouteResponse response, Action reply);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,426 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Fantasy.Assembly;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.DataStructure.Collection;
|
||||
using Fantasy.DataStructure.Dictionary;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于存储消息处理器的信息,包括类型和对象实例。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息处理器的类型</typeparam>
|
||||
internal sealed class HandlerInfo<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置消息处理器对象。
|
||||
/// </summary>
|
||||
public T Obj;
|
||||
/// <summary>
|
||||
/// 获取或设置消息处理器的类型。
|
||||
/// </summary>
|
||||
public Type Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络消息分发组件。
|
||||
/// </summary>
|
||||
public sealed class MessageDispatcherComponent : Entity, IAssembly
|
||||
{
|
||||
public long AssemblyIdentity { get; set; }
|
||||
private readonly Dictionary<Type, Type> _responseTypes = new Dictionary<Type, Type>();
|
||||
private readonly DoubleMapDictionary<uint, Type> _networkProtocols = new DoubleMapDictionary<uint, Type>();
|
||||
private readonly Dictionary<Type, IMessageHandler> _messageHandlers = new Dictionary<Type, IMessageHandler>();
|
||||
private readonly OneToManyList<long, Type> _assemblyResponseTypes = new OneToManyList<long, Type>();
|
||||
private readonly OneToManyList<long, uint> _assemblyNetworkProtocols = new OneToManyList<long, uint>();
|
||||
private readonly OneToManyList<long, HandlerInfo<IMessageHandler>> _assemblyMessageHandlers = new OneToManyList<long, HandlerInfo<IMessageHandler>>();
|
||||
#if FANTASY_UNITY
|
||||
private readonly Dictionary<Type, IMessageDelegateHandler> _messageDelegateHandlers = new Dictionary<Type, IMessageDelegateHandler>();
|
||||
#endif
|
||||
#if FANTASY_NET
|
||||
private readonly Dictionary<long, int> _customRouteMap = new Dictionary<long, int>();
|
||||
private readonly OneToManyList<long, long> _assemblyCustomRouteMap = new OneToManyList<long, long>();
|
||||
private readonly Dictionary<Type, IRouteMessageHandler> _routeMessageHandlers = new Dictionary<Type, IRouteMessageHandler>();
|
||||
private readonly OneToManyList<long, HandlerInfo<IRouteMessageHandler>> _assemblyRouteMessageHandlers = new OneToManyList<long, HandlerInfo<IRouteMessageHandler>>();
|
||||
#endif
|
||||
private CoroutineLock _receiveRouteMessageLock;
|
||||
|
||||
#region Initialize
|
||||
|
||||
internal async FTask<MessageDispatcherComponent> Initialize()
|
||||
{
|
||||
_receiveRouteMessageLock = Scene.CoroutineLockComponent.Create(GetType().TypeHandle.Value.ToInt64());
|
||||
await AssemblySystem.Register(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async FTask Load(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
LoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
private void LoadInner(long assemblyIdentity)
|
||||
{
|
||||
// 遍历所有实现了IMessage接口的类型,获取OpCode并添加到_networkProtocols字典中
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessage)))
|
||||
{
|
||||
var obj = (IMessage) Activator.CreateInstance(type);
|
||||
var opCode = obj.OpCode();
|
||||
|
||||
_networkProtocols.Add(opCode, type);
|
||||
|
||||
var responseType = type.GetProperty("ResponseType");
|
||||
|
||||
// 如果类型具有ResponseType属性,将其添加到_responseTypes字典中
|
||||
if (responseType != null)
|
||||
{
|
||||
_responseTypes.Add(type, responseType.PropertyType);
|
||||
_assemblyResponseTypes.Add(assemblyIdentity, type);
|
||||
}
|
||||
|
||||
_assemblyNetworkProtocols.Add(assemblyIdentity, opCode);
|
||||
}
|
||||
|
||||
// 遍历所有实现了IMessageHandler接口的类型,创建实例并添加到_messageHandlers字典中
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessageHandler)))
|
||||
{
|
||||
var obj = (IMessageHandler) Activator.CreateInstance(type);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
throw new Exception($"message handle {type.Name} is null");
|
||||
}
|
||||
|
||||
var key = obj.Type();
|
||||
_messageHandlers.Add(key, obj);
|
||||
_assemblyMessageHandlers.Add(assemblyIdentity, new HandlerInfo<IMessageHandler>()
|
||||
{
|
||||
Obj = obj, Type = key
|
||||
});
|
||||
}
|
||||
|
||||
// 如果编译符号FANTASY_NET存在,遍历所有实现了IRouteMessageHandler接口的类型,创建实例并添加到_routeMessageHandlers字典中
|
||||
#if FANTASY_NET
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IRouteMessageHandler)))
|
||||
{
|
||||
var obj = (IRouteMessageHandler) Activator.CreateInstance(type);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
throw new Exception($"message handle {type.Name} is null");
|
||||
}
|
||||
|
||||
var key = obj.Type();
|
||||
_routeMessageHandlers.Add(key, obj);
|
||||
_assemblyRouteMessageHandlers.Add(assemblyIdentity, new HandlerInfo<IRouteMessageHandler>()
|
||||
{
|
||||
Obj = obj, Type = key
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomRoute)))
|
||||
{
|
||||
var obj = (ICustomRoute) Activator.CreateInstance(type);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
throw new Exception($"message handle {type.Name} is null");
|
||||
}
|
||||
|
||||
var opCode = obj.OpCode();
|
||||
_customRouteMap[opCode] = obj.RouteType;
|
||||
_assemblyCustomRouteMap.Add(assemblyIdentity, opCode);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async FTask ReLoad(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
LoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
public async FTask OnUnLoad(long assemblyIdentity)
|
||||
{
|
||||
var tcs = FTask.Create(false);
|
||||
Scene?.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnUnLoadInner(assemblyIdentity);
|
||||
tcs.SetResult();
|
||||
});
|
||||
await tcs;
|
||||
}
|
||||
|
||||
private void OnUnLoadInner(long assemblyIdentity)
|
||||
{
|
||||
// 移除程序集对应的ResponseType类型和OpCode信息
|
||||
if (_assemblyResponseTypes.TryGetValue(assemblyIdentity, out var removeResponseTypes))
|
||||
{
|
||||
foreach (var removeResponseType in removeResponseTypes)
|
||||
{
|
||||
_responseTypes.Remove(removeResponseType);
|
||||
}
|
||||
|
||||
_assemblyResponseTypes.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
if (_assemblyNetworkProtocols.TryGetValue(assemblyIdentity, out var removeNetworkProtocols))
|
||||
{
|
||||
foreach (var removeNetworkProtocol in removeNetworkProtocols)
|
||||
{
|
||||
_networkProtocols.RemoveByKey(removeNetworkProtocol);
|
||||
}
|
||||
|
||||
_assemblyNetworkProtocols.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
// 移除程序集对应的消息处理器信息
|
||||
if (_assemblyMessageHandlers.TryGetValue(assemblyIdentity, out var removeMessageHandlers))
|
||||
{
|
||||
foreach (var removeMessageHandler in removeMessageHandlers)
|
||||
{
|
||||
_messageHandlers.Remove(removeMessageHandler.Type);
|
||||
}
|
||||
|
||||
_assemblyMessageHandlers.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
// 如果编译符号FANTASY_NET存在,移除程序集对应的路由消息处理器信息
|
||||
#if FANTASY_NET
|
||||
if (_assemblyRouteMessageHandlers.TryGetValue(assemblyIdentity, out var removeRouteMessageHandlers))
|
||||
{
|
||||
foreach (var removeRouteMessageHandler in removeRouteMessageHandlers)
|
||||
{
|
||||
_routeMessageHandlers.Remove(removeRouteMessageHandler.Type);
|
||||
}
|
||||
|
||||
_assemblyRouteMessageHandlers.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
|
||||
if (_assemblyCustomRouteMap.TryGetValue(assemblyIdentity, out var removeCustomRouteMap))
|
||||
{
|
||||
foreach (var removeCustom in removeCustomRouteMap)
|
||||
{
|
||||
_customRouteMap.Remove(removeCustom);
|
||||
}
|
||||
|
||||
_assemblyCustomRouteMap.RemoveByKey(assemblyIdentity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FANTASY_UNITY
|
||||
/// <summary>
|
||||
/// 手动注册一个消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="delegate"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void RegisterHandler<T>(MessageDelegate<T> @delegate) where T : IMessage
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate))
|
||||
{
|
||||
messageDelegate = new MessageDelegateHandler<T>();
|
||||
_messageDelegateHandlers.Add(type,messageDelegate);
|
||||
}
|
||||
|
||||
messageDelegate.Register(@delegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动卸载一个消息处理器,必须是通过RegisterHandler方法注册的消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="delegate"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void UnRegisterHandler<T>(MessageDelegate<T> @delegate) where T : IMessage
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageDelegate.UnRegister(@delegate) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_messageDelegateHandlers.Remove(type);
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 处理普通消息,将消息分发给相应的消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="message">消息对象</param>
|
||||
/// <param name="rpcId">RPC标识</param>
|
||||
/// <param name="protocolCode">协议码</param>
|
||||
public void MessageHandler(Session session, Type type, object message, uint rpcId, uint protocolCode)
|
||||
{
|
||||
#if FANTASY_UNITY
|
||||
if(_messageDelegateHandlers.TryGetValue(type,out var messageDelegateHandler))
|
||||
{
|
||||
messageDelegateHandler.Handle(session, message);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (!_messageHandlers.TryGetValue(type, out var messageHandler))
|
||||
{
|
||||
Log.Warning($"Scene:{session.Scene.Id} Found Unhandled Message: {message.GetType()}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用消息处理器的Handle方法并启动协程执行处理逻辑
|
||||
messageHandler.Handle(session, rpcId, protocolCode, message).Coroutine();
|
||||
}
|
||||
|
||||
// 如果编译符号FANTASY_NET存在,定义处理路由消息的方法
|
||||
#if FANTASY_NET
|
||||
/// <summary>
|
||||
/// 处理路由消息,将消息分发给相应的路由消息处理器。
|
||||
/// </summary>
|
||||
/// <param name="session">会话对象</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="entity">实体对象</param>
|
||||
/// <param name="message">消息对象</param>
|
||||
/// <param name="rpcId">RPC标识</param>
|
||||
public async FTask RouteMessageHandler(Session session, Type type, Entity entity, object message, uint rpcId)
|
||||
{
|
||||
if (!_routeMessageHandlers.TryGetValue(type, out var routeMessageHandler))
|
||||
{
|
||||
Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {message.GetType()}");
|
||||
|
||||
if (message is IRouteRequest request)
|
||||
{
|
||||
FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var runtimeId = entity.RuntimeId;
|
||||
var sessionRuntimeId = session.RuntimeId;
|
||||
|
||||
if (entity is Scene)
|
||||
{
|
||||
// 如果是Scene的话、就不要加锁了、如果加锁很一不小心就可能会造成死锁
|
||||
await routeMessageHandler.Handle(session, entity, rpcId, message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用协程锁来确保多线程安全
|
||||
using (await _receiveRouteMessageLock.Wait(runtimeId))
|
||||
{
|
||||
if (sessionRuntimeId != session.RuntimeId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtimeId != entity.RuntimeId)
|
||||
{
|
||||
if (message is IRouteRequest request)
|
||||
{
|
||||
FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await routeMessageHandler.Handle(session, entity, rpcId, message);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool GetCustomRouteType(long protocolCode, out int routeType)
|
||||
{
|
||||
return _customRouteMap.TryGetValue(protocolCode, out routeType);
|
||||
}
|
||||
#endif
|
||||
internal void FailRouteResponse(Session session, Type requestType, uint error, uint rpcId)
|
||||
{
|
||||
var response = CreateRouteResponse(requestType, error);
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
|
||||
internal IResponse CreateResponse(Type requestType, uint error)
|
||||
{
|
||||
IResponse response;
|
||||
|
||||
if (_responseTypes.TryGetValue(requestType, out var responseType))
|
||||
{
|
||||
response = (IResponse) Activator.CreateInstance(responseType);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = new Response();
|
||||
}
|
||||
|
||||
response.ErrorCode = error;
|
||||
return response;
|
||||
}
|
||||
|
||||
internal IRouteResponse CreateRouteResponse(Type requestType, uint error)
|
||||
{
|
||||
IRouteResponse response;
|
||||
|
||||
if (_responseTypes.TryGetValue(requestType, out var responseType))
|
||||
{
|
||||
response = (IRouteResponse) Activator.CreateInstance(responseType);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = new RouteResponse();
|
||||
}
|
||||
|
||||
response.ErrorCode = error;
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据消息类型获取对应的OpCode。
|
||||
/// </summary>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <returns>消息对应的OpCode</returns>
|
||||
public uint GetOpCode(Type type)
|
||||
{
|
||||
return _networkProtocols.GetKeyByValue(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据OpCode获取对应的消息类型。
|
||||
/// </summary>
|
||||
/// <param name="code">OpCode</param>
|
||||
/// <returns>OpCode对应的消息类型</returns>
|
||||
public Type GetOpCodeType(uint code)
|
||||
{
|
||||
return _networkProtocols.GetValueByKey(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示通用消息接口。
|
||||
/// </summary>
|
||||
public interface IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取消息的操作代码。
|
||||
/// </summary>
|
||||
/// <returns>操作代码。</returns>
|
||||
uint OpCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示请求消息接口。
|
||||
/// </summary>
|
||||
public interface IRequest : IMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示响应消息接口。
|
||||
/// </summary>
|
||||
public interface IResponse : IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置错误代码。
|
||||
/// </summary>
|
||||
uint ErrorCode { get; set; }
|
||||
}
|
||||
// 普通路由消息
|
||||
/// <summary>
|
||||
/// 表示普通路由消息的接口,继承自请求接口。
|
||||
/// </summary>
|
||||
public interface IRouteMessage : IRequest
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 普通路由请求接口,继承自普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface IRouteRequest : IRouteMessage { }
|
||||
/// <summary>
|
||||
/// 普通路由响应接口,继承自响应接口。
|
||||
/// </summary>
|
||||
public interface IRouteResponse : IResponse { }
|
||||
// 可寻址协议
|
||||
/// <summary>
|
||||
/// 表示可寻址协议的普通路由消息接口,继承自普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface IAddressableRouteMessage : IRouteMessage { }
|
||||
/// <summary>
|
||||
/// 可寻址协议的普通路由请求接口,继承自可寻址协议的普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface IAddressableRouteRequest : IRouteRequest { }
|
||||
/// <summary>
|
||||
/// 可寻址协议的普通路由响应接口,继承自普通路由响应接口。
|
||||
/// </summary>
|
||||
public interface IAddressableRouteResponse : IRouteResponse { }
|
||||
// 自定义Route协议
|
||||
public interface ICustomRoute : IMessage
|
||||
{
|
||||
int RouteType { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示自定义Route协议的普通路由消息接口,继承自普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface ICustomRouteMessage : IRouteMessage, ICustomRoute { }
|
||||
/// <summary>
|
||||
/// 自定义Route协议的普通路由请求接口,继承自自定义Route协议的普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface ICustomRouteRequest : IRouteRequest, ICustomRoute { }
|
||||
/// <summary>
|
||||
/// 自定义Route协议的普通路由响应接口,继承自普通路由响应接口。
|
||||
/// </summary>
|
||||
public interface ICustomRouteResponse : IRouteResponse { }
|
||||
/// <summary>
|
||||
/// 表示漫游协议的普通路由消息接口,继承自普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface IRoamingMessage : IRouteMessage, ICustomRoute { }
|
||||
/// <summary>
|
||||
/// 漫游协议的普通路由请求接口,继承自自定义Route协议的普通路由消息接口。
|
||||
/// </summary>
|
||||
public interface IRoamingRequest : IRoamingMessage { }
|
||||
/// <summary>
|
||||
/// 漫游协议的普通路由响应接口,继承自普通路由响应接口。
|
||||
/// </summary>
|
||||
public interface IRoamingResponse : IRouteResponse { }
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Serialize;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using ProtoBuf;
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Network.Roaming;
|
||||
#endif
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable PropertyCanBeMadeInitOnly.Global
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.InnerMessage
|
||||
{
|
||||
[ProtoContract]
|
||||
public sealed partial class BenchmarkMessage : AMessage, IMessage
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.BenchmarkMessage;
|
||||
}
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class BenchmarkRequest : AMessage, IRequest
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.BenchmarkRequest;
|
||||
}
|
||||
[ProtoIgnore]
|
||||
public BenchmarkResponse ResponseType { get; set; }
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public partial class BenchmarkResponse : AMessage, IResponse
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.BenchmarkResponse;
|
||||
}
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
public sealed partial class Response : AMessage, IResponse
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.DefaultResponse;
|
||||
}
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed partial class RouteResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.DefaultRouteResponse;
|
||||
}
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class PingRequest : AMessage, IRequest
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.PingRequest;
|
||||
}
|
||||
[ProtoIgnore]
|
||||
public PingResponse ResponseType { get; set; }
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public partial class PingResponse : AMessage, IResponse
|
||||
{
|
||||
public uint OpCode()
|
||||
{
|
||||
return Fantasy.Network.OpCode.PingResponse;
|
||||
}
|
||||
[ProtoMember(1)]
|
||||
public long RpcId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public long Now;
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableAdd_Request : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_AddressableAdd_Response ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableAddRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long AddressableId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public long RouteId { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public bool IsLock { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableAdd_Response : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableAddResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableGet_Request : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_AddressableGet_Response ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableGetRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long AddressableId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableGet_Response : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableGetResponse; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
[ProtoMember(1)]
|
||||
public long RouteId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableRemove_Request : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_AddressableRemove_Response ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableRemoveRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long AddressableId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableRemove_Response : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableRemoveResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableLock_Request : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_AddressableLock_Response ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableLockRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long AddressableId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableLock_Response : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableLockResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableUnLock_Request : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_AddressableUnLock_Response ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableUnLockRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long AddressableId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public long RouteId { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public string Source { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_AddressableUnLock_Response : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.AddressableUnLockResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
#if FANTASY_NET
|
||||
[ProtoContract]
|
||||
public sealed class I_LinkRoamingRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_LinkRoamingResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.LinkRoamingRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long RoamingId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public int RoamingType { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public long ForwardSessionRouteId { get; set; }
|
||||
[ProtoMember(4)]
|
||||
public long SceneRouteId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed class I_LinkRoamingResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.LinkRoamingResponse; }
|
||||
[ProtoMember(1)]
|
||||
public long TerminusId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed class I_UnLinkRoamingRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_UnLinkRoamingResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.UnLinkRoamingRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long RoamingId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public bool DisposeRoaming { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed class I_UnLinkRoamingResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.UnLinkRoamingResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_LockTerminusIdRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_LockTerminusIdResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.LockTerminusIdRequest; }
|
||||
[ProtoMember(1)]
|
||||
public long SessionRuntimeId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public int RoamingType { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_LockTerminusIdResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.LockTerminusIdResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed class I_UnLockTerminusIdRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_UnLockTerminusIdResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.UnLockTerminusIdRequest; }
|
||||
public long RouteTypeOpCode() { return 1; }
|
||||
[ProtoMember(1)]
|
||||
public long SessionRuntimeId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public int RoamingType { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public long TerminusId { get; set; }
|
||||
[ProtoMember(4)]
|
||||
public long TargetSceneRouteId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public sealed class I_UnLockTerminusIdResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.UnLockTerminusIdResponse; }
|
||||
[ProtoMember(1)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 漫游传送终端的请求
|
||||
/// </summary>
|
||||
public partial class I_TransferTerminusRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[BsonIgnore]
|
||||
public I_TransferTerminusResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.TransferTerminusRequest; }
|
||||
public Terminus Terminus { get; set; }
|
||||
}
|
||||
public partial class I_TransferTerminusResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.TransferTerminusResponse; }
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 用于服务器之间获取漫游的TerminusId。
|
||||
/// </summary>
|
||||
[ProtoContract]
|
||||
public partial class I_GetTerminusIdRequest : AMessage, IRouteRequest
|
||||
{
|
||||
[ProtoIgnore]
|
||||
public I_GetTerminusIdResponse ResponseType { get; set; }
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.GetTerminusIdRequest; }
|
||||
[ProtoMember(1)]
|
||||
public int RoamingType { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public long SessionRuntimeId { get; set; }
|
||||
}
|
||||
[ProtoContract]
|
||||
public partial class I_GetTerminusIdResponse : AMessage, IRouteResponse
|
||||
{
|
||||
public uint OpCode() { return Fantasy.Network.OpCode.GetTerminusIdResponse; }
|
||||
[ProtoMember(1)]
|
||||
public long TerminusId { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint ErrorCode { get; set; }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// BufferPacketParser消息格式化器抽象类
|
||||
/// 这个不会用在TCP协议中、因此不用考虑分包和粘包的问题。
|
||||
/// 目前这个只会用在KCP协议中、因为KCP出来的就是一个完整的包、所以可以一次性全部解析出来。
|
||||
/// 如果是用在其他协议上可能会出现问题。
|
||||
/// </summary>
|
||||
public abstract class BufferPacketParser : APacketParser
|
||||
{
|
||||
protected uint RpcId;
|
||||
protected long RouteId;
|
||||
protected uint ProtocolCode;
|
||||
protected int MessagePacketLength;
|
||||
public override void Dispose()
|
||||
{
|
||||
RpcId = 0;
|
||||
RouteId = 0;
|
||||
ProtocolCode = 0;
|
||||
MessagePacketLength = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// 解包方法
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer</param>
|
||||
/// <param name="count">count</param>
|
||||
/// <param name="packInfo">packInfo</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo);
|
||||
}
|
||||
#if FANTASY_NET
|
||||
/// <summary>
|
||||
/// 服务器之间专用的BufferPacketParser消息格式化器
|
||||
/// </summary>
|
||||
public sealed class InnerBufferPacketParser : BufferPacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BufferPacketParser.UnPack"/>
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="packInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ScanException"></exception>
|
||||
public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
|
||||
{
|
||||
packInfo = null;
|
||||
|
||||
if (buffer.Length < count)
|
||||
{
|
||||
throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
|
||||
}
|
||||
|
||||
if (count < Packet.InnerPacketHeadLength)
|
||||
{
|
||||
// 如果内存资源中的数据长度小于内部消息头的长度,无法解析
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
MessagePacketLength = *(int*)bufferPtr;
|
||||
|
||||
if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
}
|
||||
|
||||
ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength);
|
||||
RpcId = *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation);
|
||||
RouteId = *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation);
|
||||
}
|
||||
|
||||
packInfo = InnerPackInfo.Create(Network);
|
||||
packInfo.RpcId = RpcId;
|
||||
packInfo.RouteId = RouteId;
|
||||
packInfo.ProtocolCode = ProtocolCode;
|
||||
packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
|
||||
*(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message)
|
||||
{
|
||||
var memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)(bufferPtr + Packet.PacketLength) = opCode;
|
||||
*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
|
||||
*(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 客户端和服务器之间专用的BufferPacketParser消息格式化器
|
||||
/// </summary>
|
||||
public sealed class OuterBufferPacketParser : BufferPacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BufferPacketParser.UnPack"/>
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="packInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ScanException"></exception>
|
||||
public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
|
||||
{
|
||||
packInfo = null;
|
||||
|
||||
if (buffer.Length < count)
|
||||
{
|
||||
throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
|
||||
}
|
||||
|
||||
if (count < Packet.OuterPacketHeadLength)
|
||||
{
|
||||
// 如果内存资源中的数据长度小于内部消息头的长度,无法解析
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
MessagePacketLength = *(int*)bufferPtr;
|
||||
|
||||
if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
}
|
||||
|
||||
ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength);
|
||||
RpcId = *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation);
|
||||
}
|
||||
|
||||
packInfo = OuterPackInfo.Create(Network);
|
||||
packInfo.RpcId = RpcId;
|
||||
packInfo.ProtocolCode = ProtocolCode;
|
||||
packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
|
||||
{
|
||||
var memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)(bufferPtr + Packet.PacketLength) = opCode;
|
||||
*(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Webgl专用的客户端和服务器之间专用的BufferPacketParser消息格式化器
|
||||
/// </summary>
|
||||
public sealed class OuterWebglBufferPacketParser : BufferPacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BufferPacketParser.UnPack"/>
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="packInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ScanException"></exception>
|
||||
public override bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo)
|
||||
{
|
||||
packInfo = null;
|
||||
|
||||
if (buffer.Length < count)
|
||||
{
|
||||
throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}");
|
||||
}
|
||||
|
||||
if (count < Packet.OuterPacketHeadLength)
|
||||
{
|
||||
// 如果内存资源中的数据长度小于内部消息头的长度,无法解析
|
||||
return false;
|
||||
}
|
||||
|
||||
MessagePacketLength = BitConverter.ToInt32(buffer, 0);
|
||||
|
||||
if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
}
|
||||
|
||||
ProtocolCode = BitConverter.ToUInt32(buffer, Packet.PacketLength);
|
||||
RpcId = BitConverter.ToUInt32(buffer, Packet.OuterPacketRpcIdLocation);
|
||||
|
||||
packInfo = OuterPackInfo.Create(Network);
|
||||
packInfo.RpcId = RpcId;
|
||||
packInfo.ProtocolCode = ProtocolCode;
|
||||
packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
var buffer = memoryStream.GetBuffer().AsSpan();
|
||||
#if FANTASY_NET
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId);
|
||||
#endif
|
||||
#if FANTASY_UNITY
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId);
|
||||
#endif
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
|
||||
{
|
||||
var memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.UnPack);
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
var buffer = memoryStream.GetBuffer().AsSpan();
|
||||
#if FANTASY_NET
|
||||
MemoryMarshal.Write(buffer, in packetBodyCount);
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), in opCode);
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId);
|
||||
#endif
|
||||
#if FANTASY_UNITY
|
||||
MemoryMarshal.Write(buffer, ref packetBodyCount);
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), ref opCode);
|
||||
MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId);
|
||||
#endif
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// using System.Runtime.CompilerServices;
|
||||
// // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
//
|
||||
// namespace Fantasy
|
||||
// {
|
||||
// // 这个对处理分包和粘包逻辑不完整、考虑现在没有任何地方使用了、就先不修改了。
|
||||
// // 后面用到了再修改、现在这个只是留做备份、万一以后用到了呢。
|
||||
// public abstract class CircularBufferPacketParser : APacketParser
|
||||
// {
|
||||
// protected uint RpcId;
|
||||
// protected long RouteId;
|
||||
// protected uint ProtocolCode;
|
||||
// protected int MessagePacketLength;
|
||||
// protected bool IsUnPackHead = true;
|
||||
// protected readonly byte[] MessageHead = new byte[Packet.InnerPacketHeadLength];
|
||||
// public abstract bool UnPack(CircularBuffer buffer, out APackInfo packInfo);
|
||||
// }
|
||||
//
|
||||
// #if FANTASY_NET
|
||||
// public sealed class InnerCircularBufferPacketParser : CircularBufferPacketParser, IInnerPacketParser
|
||||
// {
|
||||
// public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo)
|
||||
// {
|
||||
// packInfo = null;
|
||||
//
|
||||
// // 在对象没有被释放的情况下循环解析数据
|
||||
// while (!IsDisposed)
|
||||
// {
|
||||
// if (IsUnPackHead)
|
||||
// {
|
||||
// // 如果缓冲区中的数据长度小于内部消息头的长度,无法解析
|
||||
// if (buffer.Length < Packet.InnerPacketHeadLength)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // 从缓冲区中读取内部消息头的数据
|
||||
// _ = buffer.Read(MessageHead, 0, Packet.InnerPacketHeadLength);
|
||||
// MessagePacketLength = BitConverter.ToInt32(MessageHead, 0);
|
||||
//
|
||||
// // 检查消息体长度是否超出限制
|
||||
// if (MessagePacketLength > Packet.PacketBodyMaxLength)
|
||||
// {
|
||||
// throw new ScanException(
|
||||
// $"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
// }
|
||||
//
|
||||
// // 解析协议编号、RPC ID 和 Route ID
|
||||
// ProtocolCode = BitConverter.ToUInt32(MessageHead, Packet.PacketLength);
|
||||
// RpcId = BitConverter.ToUInt32(MessageHead, Packet.InnerPacketRpcIdLocation);
|
||||
// RouteId = BitConverter.ToInt64(MessageHead, Packet.InnerPacketRouteRouteIdLocation);
|
||||
// IsUnPackHead = false;
|
||||
// }
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// // 如果缓冲区中的数据长度小于消息体的长度,无法解析
|
||||
// if (MessagePacketLength < 0 || buffer.Length < MessagePacketLength)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// IsUnPackHead = true;
|
||||
// packInfo = InnerPackInfo.Create(Network);
|
||||
// var memoryStream = packInfo.RentMemoryStream(MessagePacketLength);
|
||||
// memoryStream.SetLength(MessagePacketLength);
|
||||
// buffer.Read(memoryStream, MessagePacketLength);
|
||||
// packInfo.RpcId = RpcId;
|
||||
// packInfo.RouteId = RouteId;
|
||||
// packInfo.ProtocolCode = ProtocolCode;
|
||||
// packInfo.MessagePacketLength = MessagePacketLength;
|
||||
// return true;
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// // 在发生异常时,释放 packInfo 并记录日志
|
||||
// packInfo?.Dispose();
|
||||
// Log.Error(e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// public override MemoryStream Pack(ref uint rpcId, ref long routeTypeOpCode, ref long routeId,
|
||||
// MemoryStream memoryStream, object message)
|
||||
// {
|
||||
// return memoryStream == null
|
||||
// ? Pack(ref rpcId, ref routeId, message)
|
||||
// : Pack(ref rpcId, ref routeId, memoryStream);
|
||||
// }
|
||||
//
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private unsafe MemoryStream Pack(ref uint rpcId, ref long routeId, MemoryStream memoryStream)
|
||||
// {
|
||||
// var buffer = memoryStream.GetBuffer();
|
||||
//
|
||||
// fixed (byte* bufferPtr = buffer)
|
||||
// {
|
||||
// var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
|
||||
// var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
|
||||
// *(uint*)rpcIdPtr = rpcId;
|
||||
// *(long*)routeIdPtr = routeId;
|
||||
// }
|
||||
//
|
||||
// memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
// return memoryStream;
|
||||
// }
|
||||
//
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private unsafe MemoryStream Pack(ref uint rpcId, ref long routeId, object message)
|
||||
// {
|
||||
// var memoryStream = Network.RentMemoryStream();
|
||||
// memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
//
|
||||
// switch (message)
|
||||
// {
|
||||
// case IBsonMessage:
|
||||
// {
|
||||
// MongoHelper.SerializeTo(message, memoryStream);
|
||||
// break;
|
||||
// }
|
||||
// default:
|
||||
// {
|
||||
// ProtoBuffHelper.ToStream(message, memoryStream);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var opCode = Scene.MessageDispatcherComponent.GetOpCode(message.GetType());
|
||||
// var packetBodyCount = (int)(memoryStream.Position - Packet.InnerPacketHeadLength);
|
||||
//
|
||||
// if (packetBodyCount == 0)
|
||||
// {
|
||||
// // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
// packetBodyCount = -1;
|
||||
// }
|
||||
//
|
||||
// // 检查消息体长度是否超出限制
|
||||
// if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
// {
|
||||
// throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
// }
|
||||
//
|
||||
// var buffer = memoryStream.GetBuffer();
|
||||
//
|
||||
// fixed (byte* bufferPtr = buffer)
|
||||
// {
|
||||
// var opCodePtr = bufferPtr + Packet.PacketLength;
|
||||
// var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
|
||||
// var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
|
||||
// *(int*)bufferPtr = packetBodyCount;
|
||||
// *(uint*)opCodePtr = opCode;
|
||||
// *(uint*)rpcIdPtr = rpcId;
|
||||
// *(long*)routeIdPtr = routeId;
|
||||
// }
|
||||
//
|
||||
// memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
// return memoryStream;
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -0,0 +1,72 @@
|
||||
#if FANTASY_NET
|
||||
using System.Runtime.CompilerServices;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 打包Outer消息的帮助类
|
||||
/// </summary>
|
||||
public static class OuterBufferPacketParserHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 打包一个网络消息
|
||||
/// </summary>
|
||||
/// <param name="scene">scene</param>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个rpcId</param>
|
||||
/// <param name="message">打包的网络消息</param>
|
||||
/// <param name="memoryStreamLength">序列化后流的长度</param>
|
||||
/// <returns>打包完成会返回一个MemoryStreamBuffer</returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe MemoryStreamBuffer Pack(Scene scene, uint rpcId, IMessage message, out int memoryStreamLength)
|
||||
{
|
||||
memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = new MemoryStreamBuffer();
|
||||
memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack;
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)(bufferPtr + Packet.PacketLength) = opCode;
|
||||
*(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,357 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
internal abstract class ReadOnlyMemoryPacketParser : APacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个网络消息包
|
||||
/// </summary>
|
||||
protected APackInfo PackInfo;
|
||||
|
||||
protected int Offset;
|
||||
protected int MessageHeadOffset;
|
||||
protected int MessageBodyOffset;
|
||||
protected int MessagePacketLength;
|
||||
protected bool IsUnPackHead = true;
|
||||
protected readonly byte[] MessageHead = new byte[20];
|
||||
public ReadOnlyMemoryPacketParser() { }
|
||||
|
||||
public abstract bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo);
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Offset = 0;
|
||||
MessageHeadOffset = 0;
|
||||
MessageBodyOffset = 0;
|
||||
MessagePacketLength = 0;
|
||||
IsUnPackHead = true;
|
||||
PackInfo = null;
|
||||
Array.Clear(MessageHead, 0, 20);
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#if FANTASY_NET
|
||||
internal sealed class InnerReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser
|
||||
{
|
||||
public override unsafe bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo)
|
||||
{
|
||||
packInfo = null;
|
||||
var readOnlySpan = buffer.Span;
|
||||
var bufferLength = buffer.Length - Offset;
|
||||
|
||||
if (bufferLength == 0)
|
||||
{
|
||||
// 没有剩余的数据需要处理、等待下一个包再处理。
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsUnPackHead)
|
||||
{
|
||||
fixed (byte* bufferPtr = readOnlySpan)
|
||||
fixed (byte* messagePtr = MessageHead)
|
||||
{
|
||||
// 在当前buffer中拿到包头的数据
|
||||
var innerPacketHeadLength = Packet.InnerPacketHeadLength - MessageHeadOffset;
|
||||
var copyLength = Math.Min(bufferLength, innerPacketHeadLength);
|
||||
Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, innerPacketHeadLength, copyLength);
|
||||
Offset += copyLength;
|
||||
MessageHeadOffset += copyLength;
|
||||
// 检查是否有完整包头
|
||||
if (MessageHeadOffset == Packet.InnerPacketHeadLength)
|
||||
{
|
||||
// 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId
|
||||
MessagePacketLength = *(int*)messagePtr;
|
||||
// 检查消息体长度是否超出限制
|
||||
if (MessagePacketLength > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
}
|
||||
|
||||
PackInfo = InnerPackInfo.Create(Network);
|
||||
var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.InnerPacketHeadLength + MessagePacketLength);
|
||||
PackInfo.RpcId = *(uint*)(messagePtr + Packet.InnerPacketRpcIdLocation);
|
||||
PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength);
|
||||
PackInfo.RouteId = *(long*)(messagePtr + Packet.InnerPacketRouteRouteIdLocation);
|
||||
memoryStream.Write(MessageHead);
|
||||
IsUnPackHead = false;
|
||||
bufferLength -= copyLength;
|
||||
MessageHeadOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MessagePacketLength == -1)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packInfo = PackInfo;
|
||||
PackInfo = null;
|
||||
IsUnPackHead = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bufferLength == 0)
|
||||
{
|
||||
// 没有剩余的数据需要处理、等待下一个包再处理。
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理包消息体
|
||||
var innerPacketBodyLength = MessagePacketLength - MessageBodyOffset;
|
||||
var copyBodyLength = Math.Min(bufferLength, innerPacketBodyLength);
|
||||
// 写入数据到消息体中
|
||||
PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength));
|
||||
Offset += copyBodyLength;
|
||||
MessageBodyOffset += copyBodyLength;
|
||||
// 检查是否是完整的消息体
|
||||
if (MessageBodyOffset == MessagePacketLength)
|
||||
{
|
||||
packInfo = PackInfo;
|
||||
PackInfo = null;
|
||||
IsUnPackHead = true;
|
||||
MessageBodyOffset = 0;
|
||||
return true;
|
||||
}
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
|
||||
*(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message)
|
||||
{
|
||||
var memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
// 其实可以不用设置-1、解包的时候判断如果是0也可以、但我仔细想了下,还是用-1代表更加清晰。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)(bufferPtr + Packet.PacketLength) = opCode;
|
||||
*(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId;
|
||||
*(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
internal sealed class OuterReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser
|
||||
{
|
||||
public override unsafe bool UnPack(ref ReadOnlyMemory<byte> buffer, out APackInfo packInfo)
|
||||
{
|
||||
packInfo = null;
|
||||
var readOnlySpan = buffer.Span;
|
||||
var bufferLength = buffer.Length - Offset;
|
||||
|
||||
if (bufferLength == 0)
|
||||
{
|
||||
// 没有剩余的数据需要处理、等待下一个包再处理。
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsUnPackHead)
|
||||
{
|
||||
fixed (byte* bufferPtr = readOnlySpan)
|
||||
fixed (byte* messagePtr = MessageHead)
|
||||
{
|
||||
// 在当前buffer中拿到包头的数据
|
||||
var outerPacketHeadLength = Packet.OuterPacketHeadLength - MessageHeadOffset;
|
||||
var copyLength = Math.Min(bufferLength, outerPacketHeadLength);
|
||||
Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, outerPacketHeadLength, copyLength);
|
||||
Offset += copyLength;
|
||||
MessageHeadOffset += copyLength;
|
||||
// 检查是否有完整包头
|
||||
if (MessageHeadOffset == Packet.OuterPacketHeadLength)
|
||||
{
|
||||
// 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId
|
||||
MessagePacketLength = *(int*)messagePtr;
|
||||
// 检查消息体长度是否超出限制
|
||||
if (MessagePacketLength > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}");
|
||||
}
|
||||
|
||||
PackInfo = OuterPackInfo.Create(Network);
|
||||
PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength);
|
||||
PackInfo.RpcId = *(uint*)(messagePtr + Packet.OuterPacketRpcIdLocation);
|
||||
var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.OuterPacketHeadLength + MessagePacketLength);
|
||||
memoryStream.Write(MessageHead);
|
||||
IsUnPackHead = false;
|
||||
bufferLength -= copyLength;
|
||||
MessageHeadOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MessagePacketLength == -1)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packInfo = PackInfo;
|
||||
PackInfo = null;
|
||||
IsUnPackHead = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bufferLength == 0)
|
||||
{
|
||||
// 没有剩余的数据需要处理、等待下一个包再处理。
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
// 处理包消息体
|
||||
var outerPacketBodyLength = MessagePacketLength - MessageBodyOffset;
|
||||
var copyBodyLength = Math.Min(bufferLength, outerPacketBodyLength);
|
||||
// 写入数据到消息体中
|
||||
PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength));
|
||||
Offset += copyBodyLength;
|
||||
MessageBodyOffset += copyBodyLength;
|
||||
// 检查是否是完整的消息体
|
||||
if (MessageBodyOffset == MessagePacketLength)
|
||||
{
|
||||
packInfo = PackInfo;
|
||||
PackInfo = null;
|
||||
IsUnPackHead = true;
|
||||
MessageBodyOffset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Offset = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message)
|
||||
{
|
||||
var memoryStreamLength = 0;
|
||||
var messageType = message.GetType();
|
||||
var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack);
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
fixed (byte* bufferPtr = memoryStream.GetBuffer())
|
||||
{
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)(bufferPtr + Packet.PacketLength) = opCode;
|
||||
*(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId;
|
||||
}
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.PacketParser.Interface
|
||||
{
|
||||
public abstract class APackInfo : IDisposable
|
||||
{
|
||||
internal ANetwork Network;
|
||||
|
||||
public uint RpcId;
|
||||
public long RouteId;
|
||||
public long PackInfoId;
|
||||
public bool IsDisposed;
|
||||
private uint _protocolCode;
|
||||
|
||||
public uint ProtocolCode
|
||||
{
|
||||
get => _protocolCode;
|
||||
set
|
||||
{
|
||||
_protocolCode = value;
|
||||
OpCodeIdStruct = value;
|
||||
}
|
||||
}
|
||||
public OpCodeIdStruct OpCodeIdStruct { get; private set; }
|
||||
public MemoryStreamBuffer MemoryStream { get; protected set; }
|
||||
public abstract object Deserialize(Type messageType);
|
||||
public abstract MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0);
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RpcId = 0;
|
||||
RouteId = 0;
|
||||
PackInfoId = 0;
|
||||
ProtocolCode = 0;
|
||||
_protocolCode = 0;
|
||||
OpCodeIdStruct = default;
|
||||
|
||||
if (MemoryStream != null)
|
||||
{
|
||||
Network.MemoryStreamBufferPool.ReturnMemoryStream(MemoryStream);
|
||||
MemoryStream = null;
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
Network = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.PacketParser.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象的包解析器基类,用于解析网络通信数据包。
|
||||
/// </summary>
|
||||
public abstract class APacketParser : IDisposable
|
||||
{
|
||||
internal Scene Scene;
|
||||
internal ANetwork Network;
|
||||
internal MessageDispatcherComponent MessageDispatcherComponent;
|
||||
protected bool IsDisposed { get; private set; }
|
||||
public abstract MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message);
|
||||
public virtual void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
Scene = null;
|
||||
MessageDispatcherComponent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
public struct OpCodeIdStruct
|
||||
{
|
||||
// OpCodeIdStruct:5 + 4 + 23 = 32
|
||||
// +-------------------------+-------------------------------------------+-----------------------------+
|
||||
// | protocol(5) 最多31种类型 | OpCodeProtocolType(4) 最多15种不同的网络协议 | Index(23) 最多8388607个协议 |
|
||||
// +-------------------------+-------------------------------------------+-----------------------------+
|
||||
public uint OpCodeProtocolType { get; private set; }
|
||||
public uint Protocol { get; private set; }
|
||||
public uint Index { get; private set; }
|
||||
|
||||
public OpCodeIdStruct(uint opCodeProtocolType, uint protocol, uint index)
|
||||
{
|
||||
OpCodeProtocolType = opCodeProtocolType;
|
||||
Protocol = protocol;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public static implicit operator uint(OpCodeIdStruct opCodeIdStruct)
|
||||
{
|
||||
var result = opCodeIdStruct.Index;
|
||||
result |= opCodeIdStruct.OpCodeProtocolType << 23;
|
||||
result |= opCodeIdStruct.Protocol << 27;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static implicit operator OpCodeIdStruct(uint opCodeId)
|
||||
{
|
||||
var opCodeIdStruct = new OpCodeIdStruct()
|
||||
{
|
||||
Index = opCodeId & 0x7FFFFF
|
||||
};
|
||||
opCodeId >>= 23;
|
||||
opCodeIdStruct.OpCodeProtocolType = opCodeId & 0xF;
|
||||
opCodeId >>= 4;
|
||||
opCodeIdStruct.Protocol = opCodeId & 0x1F;
|
||||
return opCodeIdStruct;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpCodeProtocolType
|
||||
{
|
||||
public const uint Bson = 1;
|
||||
public const uint ProtoBuf = 0;
|
||||
}
|
||||
|
||||
public static class OpCodeType
|
||||
{
|
||||
public const uint OuterMessage = 1;
|
||||
public const uint OuterRequest = 2;
|
||||
public const uint OuterResponse = 3;
|
||||
|
||||
public const uint InnerMessage = 4;
|
||||
public const uint InnerRequest = 5;
|
||||
public const uint InnerResponse = 6;
|
||||
|
||||
public const uint InnerRouteMessage = 7;
|
||||
public const uint InnerRouteRequest = 8;
|
||||
public const uint InnerRouteResponse = 9;
|
||||
|
||||
public const uint OuterAddressableMessage = 10;
|
||||
public const uint OuterAddressableRequest = 11;
|
||||
public const uint OuterAddressableResponse = 12;
|
||||
|
||||
public const uint InnerAddressableMessage = 13;
|
||||
public const uint InnerAddressableRequest = 14;
|
||||
public const uint InnerAddressableResponse = 15;
|
||||
|
||||
public const uint OuterCustomRouteMessage = 16;
|
||||
public const uint OuterCustomRouteRequest = 17;
|
||||
public const uint OuterCustomRouteResponse = 18;
|
||||
|
||||
public const uint OuterRoamingMessage = 19;
|
||||
public const uint OuterRoamingRequest = 20;
|
||||
public const uint OuterRoamingResponse = 21;
|
||||
|
||||
public const uint InnerRoamingMessage = 22;
|
||||
public const uint InnerRoamingRequest = 23;
|
||||
public const uint InnerRoamingResponse = 24;
|
||||
|
||||
public const uint OuterPingRequest = 30;
|
||||
public const uint OuterPingResponse = 31;
|
||||
}
|
||||
|
||||
public static class OpCode
|
||||
{
|
||||
public static readonly uint BenchmarkMessage = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterMessage, 8388607);
|
||||
public static readonly uint BenchmarkRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterRequest, 8388607);
|
||||
public static readonly uint BenchmarkResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterResponse, 8388607);
|
||||
public static readonly uint PingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingRequest, 1);
|
||||
public static readonly uint PingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingResponse, 1);
|
||||
public static readonly uint DefaultResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerResponse, 1);
|
||||
public static readonly uint DefaultRouteResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 7);
|
||||
public static readonly uint AddressableAddRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 1);
|
||||
public static readonly uint AddressableAddResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 1);
|
||||
public static readonly uint AddressableGetRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 2);
|
||||
public static readonly uint AddressableGetResponse = Create(OpCodeProtocolType.ProtoBuf,OpCodeType.InnerRouteResponse,2);
|
||||
public static readonly uint AddressableRemoveRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 3);
|
||||
public static readonly uint AddressableRemoveResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 3);
|
||||
public static readonly uint AddressableLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 4);
|
||||
public static readonly uint AddressableLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 4);
|
||||
public static readonly uint AddressableUnLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 5);
|
||||
public static readonly uint AddressableUnLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 5);
|
||||
public static readonly uint LinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 6);
|
||||
public static readonly uint LinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 6);
|
||||
public static readonly uint UnLinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 8);
|
||||
public static readonly uint UnLinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 8);
|
||||
public static readonly uint LockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 9);
|
||||
public static readonly uint LockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 9);
|
||||
public static readonly uint UnLockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 10);
|
||||
public static readonly uint UnLockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 10);
|
||||
public static readonly uint GetTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 11);
|
||||
public static readonly uint GetTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 11);
|
||||
|
||||
public static readonly uint TransferTerminusRequest = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteRequest, 1);
|
||||
public static readonly uint TransferTerminusResponse = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteResponse, 1);
|
||||
|
||||
public static uint Create(uint opCodeProtocolType, uint protocol, uint index)
|
||||
{
|
||||
return new OpCodeIdStruct(opCodeProtocolType, protocol, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Pool;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
public sealed class InnerPackInfo : APackInfo
|
||||
{
|
||||
private readonly Dictionary<Type, Func<object>> _createInstances = new Dictionary<Type, Func<object>>();
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var network = Network;
|
||||
base.Dispose();
|
||||
network.ReturnInnerPackInfo(this);
|
||||
}
|
||||
|
||||
public static InnerPackInfo Create(ANetwork network)
|
||||
{
|
||||
var innerPackInfo = network.RentInnerPackInfo();
|
||||
innerPackInfo.Network = network;
|
||||
innerPackInfo.IsDisposed = false;
|
||||
return innerPackInfo;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
|
||||
{
|
||||
return MemoryStream ??= Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size);
|
||||
}
|
||||
|
||||
public override object Deserialize(Type messageType)
|
||||
{
|
||||
if (MemoryStream == null)
|
||||
{
|
||||
Log.Debug("Deserialize MemoryStream is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (MemoryStream.Length == 0)
|
||||
{
|
||||
if (_createInstances.TryGetValue(messageType, out var createInstance))
|
||||
{
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
createInstance = CreateInstance.CreateObject(messageType);
|
||||
_createInstances.Add(messageType, createInstance);
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
var obj = serializer.Deserialize(messageType, MemoryStream);
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return obj;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
public sealed class OuterPackInfo : APackInfo
|
||||
{
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var network = Network;
|
||||
base.Dispose();
|
||||
network.ReturnOuterPackInfo(this);
|
||||
}
|
||||
|
||||
public static OuterPackInfo Create(ANetwork network)
|
||||
{
|
||||
var outerPackInfo = network.RentOuterPackInfo();
|
||||
outerPackInfo.Network = network;
|
||||
outerPackInfo.IsDisposed = false;
|
||||
return outerPackInfo;
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
|
||||
{
|
||||
if (MemoryStream == null)
|
||||
{
|
||||
MemoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size);
|
||||
}
|
||||
|
||||
return MemoryStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将消息数据从内存反序列化为指定的消息类型实例。
|
||||
/// </summary>
|
||||
/// <param name="messageType">目标消息类型。</param>
|
||||
/// <returns>反序列化后的消息类型实例。</returns>
|
||||
public override object Deserialize(Type messageType)
|
||||
{
|
||||
if (MemoryStream == null)
|
||||
{
|
||||
Log.Debug("Deserialize MemoryStream is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
var obj = serializer.Deserialize(messageType, MemoryStream);
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return obj;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#if FANTASY_NET
|
||||
using System.Collections.Concurrent;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Pool;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
public sealed class ProcessPackInfo : APackInfo
|
||||
{
|
||||
private int _disposeCount;
|
||||
public Type MessageType { get; private set; }
|
||||
private static readonly ConcurrentQueue<ProcessPackInfo> Caches = new ConcurrentQueue<ProcessPackInfo>();
|
||||
private readonly ConcurrentDictionary<Type, Func<object>> _createInstances = new ConcurrentDictionary<Type, Func<object>>();
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (--_disposeCount > 0 || IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposeCount = 0;
|
||||
MessageType = null;
|
||||
base.Dispose();
|
||||
|
||||
if (Caches.Count > 2000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Caches.Enqueue(this);
|
||||
}
|
||||
|
||||
public static unsafe ProcessPackInfo Create<T>(Scene scene, T message, int disposeCount, uint rpcId = 0, long routeId = 0) where T : IRouteMessage
|
||||
{
|
||||
if (!Caches.TryDequeue(out var packInfo))
|
||||
{
|
||||
packInfo = new ProcessPackInfo();
|
||||
}
|
||||
|
||||
var type = typeof(T);
|
||||
var memoryStreamLength = 0;
|
||||
packInfo._disposeCount = disposeCount;
|
||||
packInfo.MessageType = type;
|
||||
packInfo.IsDisposed = false;
|
||||
var memoryStream = new MemoryStreamBuffer();
|
||||
memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack;
|
||||
OpCodeIdStruct opCodeIdStruct = message.OpCode();
|
||||
memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(type, message, memoryStream);
|
||||
memoryStreamLength = (int)memoryStream.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"type:{type} Does not support processing protocol");
|
||||
}
|
||||
|
||||
var opCode = scene.MessageDispatcherComponent.GetOpCode(packInfo.MessageType);
|
||||
var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength;
|
||||
|
||||
if (packetBodyCount == 0)
|
||||
{
|
||||
// protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。
|
||||
// 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。
|
||||
packetBodyCount = -1;
|
||||
}
|
||||
|
||||
if (packetBodyCount > Packet.PacketBodyMaxLength)
|
||||
{
|
||||
// 检查消息体长度是否超出限制
|
||||
throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes");
|
||||
}
|
||||
|
||||
var buffer = memoryStream.GetBuffer();
|
||||
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
var opCodePtr = bufferPtr + Packet.PacketLength;
|
||||
var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
|
||||
var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
|
||||
*(int*)bufferPtr = packetBodyCount;
|
||||
*(uint*)opCodePtr = opCode;
|
||||
*(uint*)rpcIdPtr = rpcId;
|
||||
*(long*)routeIdPtr = routeId;
|
||||
}
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
packInfo.MemoryStream = memoryStream;
|
||||
return packInfo;
|
||||
}
|
||||
|
||||
public unsafe void Set(uint rpcId, long routeId)
|
||||
{
|
||||
var buffer = MemoryStream.GetBuffer();
|
||||
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation;
|
||||
var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation;
|
||||
*(uint*)rpcIdPtr = rpcId;
|
||||
*(long*)routeIdPtr = routeId;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object Deserialize(Type messageType)
|
||||
{
|
||||
if (MemoryStream == null)
|
||||
{
|
||||
Log.Debug("Deserialize MemoryStream is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
object obj = null;
|
||||
MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
|
||||
|
||||
if (MemoryStream.Length == 0)
|
||||
{
|
||||
if (_createInstances.TryGetValue(messageType, out var createInstance))
|
||||
{
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
createInstance = CreateInstance.CreateObject(messageType);
|
||||
_createInstances.TryAdd(messageType, createInstance);
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
obj = serializer.Deserialize(messageType, MemoryStream);
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return obj;
|
||||
}
|
||||
|
||||
MemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供关于消息包的常量定义。
|
||||
/// </summary>
|
||||
public struct Packet
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息体最大长度
|
||||
/// </summary>
|
||||
public const int PacketBodyMaxLength = ushort.MaxValue * 16;
|
||||
/// <summary>
|
||||
/// 消息体长度在消息头占用的长度
|
||||
/// </summary>
|
||||
public const int PacketLength = sizeof(int);
|
||||
/// <summary>
|
||||
/// 协议编号在消息头占用的长度
|
||||
/// </summary>
|
||||
public const int ProtocolCodeLength = sizeof(uint);
|
||||
/// <summary>
|
||||
/// RouteId长度
|
||||
/// </summary>
|
||||
public const int PacketRouteIdLength = sizeof(long);
|
||||
/// <summary>
|
||||
/// RpcId在消息头占用的长度
|
||||
/// </summary>
|
||||
public const int RpcIdLength = sizeof(uint);
|
||||
/// <summary>
|
||||
/// OuterRPCId所在的位置
|
||||
/// </summary>
|
||||
public const int OuterPacketRpcIdLocation = PacketLength + ProtocolCodeLength;
|
||||
/// <summary>
|
||||
/// InnerRPCId所在的位置
|
||||
/// </summary>
|
||||
public const int InnerPacketRpcIdLocation = PacketLength + ProtocolCodeLength;
|
||||
/// <summary>
|
||||
/// RouteId所在的位置
|
||||
/// </summary>
|
||||
public const int InnerPacketRouteRouteIdLocation = PacketLength + ProtocolCodeLength + RpcIdLength;
|
||||
/// <summary>
|
||||
/// 外网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度)
|
||||
/// </summary>
|
||||
public const int OuterPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength;
|
||||
/// <summary>
|
||||
/// 内网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度)
|
||||
/// </summary>
|
||||
public const int InnerPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
|
||||
// ReSharper disable PossibleNullReferenceException
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
namespace Fantasy.PacketParser
|
||||
{
|
||||
internal static class PacketParserFactory
|
||||
{
|
||||
#if FANTASY_NET
|
||||
internal static ReadOnlyMemoryPacketParser CreateServerReadOnlyMemoryPacket(ANetwork network)
|
||||
{
|
||||
ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null;
|
||||
|
||||
switch (network.NetworkTarget)
|
||||
{
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser();
|
||||
break;
|
||||
}
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readOnlyMemoryPacketParser.Scene = network.Scene;
|
||||
readOnlyMemoryPacketParser.Network = network;
|
||||
readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return readOnlyMemoryPacketParser;
|
||||
}
|
||||
|
||||
public static BufferPacketParser CreateServerBufferPacket(ANetwork network)
|
||||
{
|
||||
BufferPacketParser bufferPacketParser = null;
|
||||
|
||||
switch (network.NetworkTarget)
|
||||
{
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
bufferPacketParser = new InnerBufferPacketParser();
|
||||
break;
|
||||
}
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
bufferPacketParser = new OuterBufferPacketParser();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bufferPacketParser.Scene = network.Scene;
|
||||
bufferPacketParser.Network = network;
|
||||
bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return bufferPacketParser;
|
||||
}
|
||||
#endif
|
||||
internal static ReadOnlyMemoryPacketParser CreateClientReadOnlyMemoryPacket(ANetwork network)
|
||||
{
|
||||
ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null;
|
||||
|
||||
switch (network.NetworkTarget)
|
||||
{
|
||||
#if FANTASY_NET
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readOnlyMemoryPacketParser.Scene = network.Scene;
|
||||
readOnlyMemoryPacketParser.Network = network;
|
||||
readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return readOnlyMemoryPacketParser;
|
||||
}
|
||||
|
||||
#if !FANTASY_WEBGL
|
||||
public static BufferPacketParser CreateClientBufferPacket(ANetwork network)
|
||||
{
|
||||
BufferPacketParser bufferPacketParser = null;
|
||||
|
||||
switch (network.NetworkTarget)
|
||||
{
|
||||
#if FANTASY_NET
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
bufferPacketParser = new InnerBufferPacketParser();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
bufferPacketParser = new OuterBufferPacketParser();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bufferPacketParser.Scene = network.Scene;
|
||||
bufferPacketParser.Network = network;
|
||||
bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return bufferPacketParser;
|
||||
}
|
||||
#endif
|
||||
public static T CreateClient<T>(ANetwork network) where T : APacketParser
|
||||
{
|
||||
var packetParserType = typeof(T);
|
||||
|
||||
switch (network.NetworkTarget)
|
||||
{
|
||||
#if FANTASY_NET
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
APacketParser innerPacketParser = null;
|
||||
|
||||
if (packetParserType == typeof(ReadOnlyMemoryPacketParser))
|
||||
{
|
||||
innerPacketParser = new InnerReadOnlyMemoryPacketParser();
|
||||
}
|
||||
else if (packetParserType == typeof(BufferPacketParser))
|
||||
{
|
||||
innerPacketParser = new InnerBufferPacketParser();
|
||||
}
|
||||
// else if(packetParserType == typeof(CircularBufferPacketParser))
|
||||
// {
|
||||
// innerPacketParser = new InnerCircularBufferPacketParser();
|
||||
// }
|
||||
|
||||
innerPacketParser.Scene = network.Scene;
|
||||
innerPacketParser.Network = network;
|
||||
innerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return (T)innerPacketParser;
|
||||
}
|
||||
#endif
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
APacketParser outerPacketParser = null;
|
||||
|
||||
if (packetParserType == typeof(ReadOnlyMemoryPacketParser))
|
||||
{
|
||||
outerPacketParser = new OuterReadOnlyMemoryPacketParser();
|
||||
}
|
||||
else if (packetParserType == typeof(BufferPacketParser))
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
outerPacketParser = new OuterWebglBufferPacketParser();
|
||||
#else
|
||||
outerPacketParser = new OuterBufferPacketParser();
|
||||
#endif
|
||||
}
|
||||
outerPacketParser.Scene = network.Scene;
|
||||
outerPacketParser.Network = network;
|
||||
outerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent;
|
||||
return (T)outerPacketParser;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
#if FANTASY_UNITY || FANTASY_CONSOLE
|
||||
/// <summary>
|
||||
/// 提供了一个用于客户端网络消息调度和处理的抽象基类。
|
||||
/// </summary>
|
||||
public sealed class ClientMessageScheduler : ANetworkMessageScheduler
|
||||
{
|
||||
public ClientMessageScheduler(Scene scene) : base(scene) { }
|
||||
|
||||
public override async FTask Scheduler(Session session, APackInfo packInfo)
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
switch (packInfo.OpCodeIdStruct.Protocol)
|
||||
{
|
||||
case OpCodeType.OuterMessage:
|
||||
case OpCodeType.OuterRequest:
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
case OpCodeType.OuterAddressableRequest:
|
||||
case OpCodeType.OuterCustomRouteMessage:
|
||||
case OpCodeType.OuterCustomRouteRequest:
|
||||
case OpCodeType.OuterRoamingMessage:
|
||||
case OpCodeType.OuterRoamingRequest:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterResponse:
|
||||
case OpCodeType.OuterPingResponse:
|
||||
case OpCodeType.OuterAddressableResponse:
|
||||
case OpCodeType.OuterCustomRouteResponse:
|
||||
case OpCodeType.OuterRoamingResponse:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
// 这个一般是客户端Session.Call发送时使用的、目前这个逻辑只有Unity客户端时使用
|
||||
|
||||
var aResponse = (IResponse)packInfo.Deserialize(messageType);
|
||||
|
||||
if (!session.RequestCallback.Remove(packInfo.RpcId, out var action))
|
||||
{
|
||||
Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
action.SetResult(aResponse);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
packInfo.Dispose();
|
||||
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if FANTASY_NET
|
||||
internal sealed class ClientMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene)
|
||||
{
|
||||
public override FTask Scheduler(Session session, APackInfo packInfo)
|
||||
{
|
||||
throw new NotSupportedException($"ClientMessageScheduler Received unsupported message protocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#if FANTASY_NET
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
using System.Runtime.CompilerServices;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供了一个机制来调度和处理内部网络消息。
|
||||
/// </summary>
|
||||
internal sealed class InnerMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene)
|
||||
{
|
||||
public override async FTask Scheduler(Session session, APackInfo packInfo)
|
||||
{
|
||||
var protocol = packInfo.OpCodeIdStruct.Protocol;
|
||||
|
||||
switch (protocol)
|
||||
{
|
||||
case OpCodeType.InnerMessage:
|
||||
case OpCodeType.InnerRequest:
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
try
|
||||
{
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"ANetworkMessageScheduler OuterResponse error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerResponse:
|
||||
case OpCodeType.InnerRouteResponse:
|
||||
case OpCodeType.InnerAddressableResponse:
|
||||
case OpCodeType.InnerRoamingResponse:
|
||||
case OpCodeType.OuterAddressableResponse:
|
||||
case OpCodeType.OuterCustomRouteResponse:
|
||||
case OpCodeType.OuterRoamingResponse:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
NetworkMessagingComponent.ResponseHandler(packInfo.RpcId, (IResponse)packInfo.Deserialize(messageType));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerRouteMessage:
|
||||
case OpCodeType.InnerAddressableMessage:
|
||||
case OpCodeType.InnerRoamingMessage:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
if (!Scene.TryGetEntity(packInfo.RouteId, out var entity))
|
||||
{
|
||||
Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId);
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = packInfo.Deserialize(messageType);
|
||||
await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerRouteRequest:
|
||||
case OpCodeType.InnerAddressableRequest:
|
||||
case OpCodeType.InnerRoamingRequest:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
if (!Scene.TryGetEntity(packInfo.RouteId, out var entity))
|
||||
{
|
||||
Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId);
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = packInfo.Deserialize(messageType);
|
||||
await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterCustomRouteRequest:
|
||||
case OpCodeType.OuterAddressableRequest:
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
case OpCodeType.OuterCustomRouteMessage:
|
||||
case OpCodeType.OuterRoamingMessage:
|
||||
case OpCodeType.OuterRoamingRequest:
|
||||
{
|
||||
var entity = Scene.GetEntity(packInfo.RouteId);
|
||||
|
||||
switch (entity)
|
||||
{
|
||||
case null:
|
||||
{
|
||||
// 执行到这里有两种情况:
|
||||
using (packInfo)
|
||||
{
|
||||
switch (Scene.SceneConfig.SceneTypeString)
|
||||
{
|
||||
case "Gate":
|
||||
{
|
||||
// 1、当前是Gate进行,需要转发消息给客户端,但当前这个Session已经断开了。
|
||||
// 这种情况不需要做任何处理。
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// 2、当前是其他Scene、消息通过Gate发送到这个Scene上面,但这个Scene上面没有这个Entity。
|
||||
// 因为这个是Gate转发消息到这个Scene的,如果没有找到Entity要返回错误给Gate。
|
||||
// 出现这个情况一定要打印日志,因为出现这个问题肯定是上层逻辑导致的,不应该出现这样的问题。
|
||||
var packInfoRouteId = packInfo.RouteId;
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
switch (protocol)
|
||||
{
|
||||
case OpCodeType.OuterCustomRouteRequest:
|
||||
case OpCodeType.OuterAddressableRequest:
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
{
|
||||
Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"The Entity associated with RouteId = {packInfoRouteId} was not found! messageType = {messageType.FullName} protocol = {protocol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case Session gateSession:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
// 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发消息
|
||||
gateSession.Send(packInfo.MemoryStream, packInfo.RpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var obj = packInfo.Deserialize(messageType);
|
||||
await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
var infoProtocolCode = packInfo.ProtocolCode;
|
||||
packInfo.Dispose();
|
||||
throw new NotSupportedException($"InnerMessageScheduler Received unsupported message protocolCode:{infoProtocolCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
// ReSharper disable UnassignedField.Global
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
public abstract class ANetworkMessageScheduler
|
||||
{
|
||||
protected readonly Scene Scene;
|
||||
protected readonly MessageDispatcherComponent MessageDispatcherComponent;
|
||||
#if FANTASY_NET
|
||||
protected readonly NetworkMessagingComponent NetworkMessagingComponent;
|
||||
#endif
|
||||
protected ANetworkMessageScheduler(Scene scene)
|
||||
{
|
||||
Scene = scene;
|
||||
MessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
#if FANTASY_NET
|
||||
NetworkMessagingComponent = scene.NetworkMessagingComponent;
|
||||
#endif
|
||||
}
|
||||
public abstract FTask Scheduler(Session session, APackInfo packInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#pragma warning disable CS8625
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络消息发送者的类。
|
||||
/// </summary>
|
||||
public struct MessageSender : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 RPC ID。
|
||||
/// </summary>
|
||||
public uint RpcId { get; private set; }
|
||||
/// <summary>
|
||||
/// 获取或设置路由 ID。
|
||||
/// </summary>
|
||||
public long RouteId { get; private set; }
|
||||
/// <summary>
|
||||
/// 获取或设置创建时间。
|
||||
/// </summary>
|
||||
public long CreateTime { get; private set; }
|
||||
/// <summary>
|
||||
/// 获取或设置消息类型。
|
||||
/// </summary>
|
||||
public Type MessageType { get; private set; }
|
||||
/// <summary>
|
||||
/// 获取或设置请求消息。
|
||||
/// </summary>
|
||||
public IMessage Request { get; private set; }
|
||||
/// <summary>
|
||||
/// 获取或设置任务。
|
||||
/// </summary>
|
||||
public FTask<IResponse> Tcs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
RpcId = 0;
|
||||
RouteId = 0;
|
||||
CreateTime = 0;
|
||||
Tcs = null;
|
||||
Request = null;
|
||||
MessageType = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="MessageSender"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="rpcId">RPC ID。</param>
|
||||
/// <param name="requestType">请求消息类型。</param>
|
||||
/// <param name="tcs">任务。</param>
|
||||
/// <returns>创建的 <see cref="MessageSender"/> 实例。</returns>
|
||||
public static MessageSender Create(uint rpcId, Type requestType, FTask<IResponse> tcs)
|
||||
{
|
||||
var routeMessageSender = new MessageSender();
|
||||
routeMessageSender.Tcs = tcs;
|
||||
routeMessageSender.RpcId = rpcId;
|
||||
routeMessageSender.MessageType = requestType;
|
||||
routeMessageSender.CreateTime = TimeHelper.Now;
|
||||
return routeMessageSender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="MessageSender"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="rpcId">RPC ID。</param>
|
||||
/// <param name="request">请求消息。</param>
|
||||
/// <param name="tcs">任务。</param>
|
||||
/// <returns>创建的 <see cref="MessageSender"/> 实例。</returns>
|
||||
public static MessageSender Create(uint rpcId, IRequest request, FTask<IResponse> tcs)
|
||||
{
|
||||
var routeMessageSender = new MessageSender();
|
||||
routeMessageSender.Tcs = tcs;
|
||||
routeMessageSender.RpcId = rpcId;
|
||||
routeMessageSender.Request = request;
|
||||
routeMessageSender.CreateTime = TimeHelper.Now;
|
||||
return routeMessageSender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="MessageSender"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="rpcId">RPC ID。</param>
|
||||
/// <param name="routeId">路由 ID。</param>
|
||||
/// <param name="request">路由消息请求。</param>
|
||||
/// <param name="tcs">任务。</param>
|
||||
/// <returns>创建的 <see cref="MessageSender"/> 实例。</returns>
|
||||
public static MessageSender Create(uint rpcId, long routeId, IRouteMessage request, FTask<IResponse> tcs)
|
||||
{
|
||||
var routeMessageSender = new MessageSender();
|
||||
routeMessageSender.Tcs = tcs;
|
||||
routeMessageSender.RpcId = rpcId;
|
||||
routeMessageSender.RouteId = routeId;
|
||||
routeMessageSender.Request = request;
|
||||
routeMessageSender.CreateTime = TimeHelper.Now;
|
||||
return routeMessageSender;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Entitas;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Timer;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
public struct NetworkMessageUpdate
|
||||
{
|
||||
public NetworkMessagingComponent NetworkMessagingComponent;
|
||||
}
|
||||
|
||||
public class NetworkMessagingComponentAwakeSystem : AwakeSystem<NetworkMessagingComponent>
|
||||
{
|
||||
protected override void Awake(NetworkMessagingComponent self)
|
||||
{
|
||||
var selfScene = self.Scene;
|
||||
self.TimerComponent = selfScene.TimerComponent;
|
||||
self.MessageDispatcherComponent = selfScene.MessageDispatcherComponent;
|
||||
self.AddressableRouteMessageLock = selfScene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64());
|
||||
|
||||
self.TimerId = self.TimerComponent.Net.RepeatedTimer(10000, new NetworkMessageUpdate()
|
||||
{
|
||||
NetworkMessagingComponent = self
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkMessagingComponentDestroySystem : DestroySystem<NetworkMessagingComponent>
|
||||
{
|
||||
protected override void Destroy(NetworkMessagingComponent self)
|
||||
{
|
||||
if (self.TimerId != 0)
|
||||
{
|
||||
self.TimerComponent.Net.Remove(ref self.TimerId);
|
||||
}
|
||||
|
||||
foreach (var (rpcId, messageSender) in self.RequestCallback.ToDictionary())
|
||||
{
|
||||
self.ReturnMessageSender(rpcId, messageSender);
|
||||
}
|
||||
|
||||
self.AddressableRouteMessageLock.Dispose();
|
||||
|
||||
self.RequestCallback.Clear();
|
||||
self.TimeoutRouteMessageSenders.Clear();
|
||||
self.TimerComponent = null;
|
||||
self.MessageDispatcherComponent = null;
|
||||
self.AddressableRouteMessageLock = null;
|
||||
}
|
||||
}
|
||||
public sealed class NetworkMessagingComponent : Entity
|
||||
{
|
||||
public long TimerId;
|
||||
private uint _rpcId;
|
||||
public CoroutineLock AddressableRouteMessageLock;
|
||||
public TimerComponent TimerComponent;
|
||||
public MessageDispatcherComponent MessageDispatcherComponent;
|
||||
public readonly SortedDictionary<uint, MessageSender> RequestCallback = new();
|
||||
public readonly Dictionary<uint, MessageSender> TimeoutRouteMessageSenders = new();
|
||||
|
||||
public void SendInnerRoute(long routeId, IRouteMessage message)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
Log.Error($"SendInnerRoute appId == 0");
|
||||
return;
|
||||
}
|
||||
|
||||
Scene.GetSession(routeId).Send(message, 0, routeId);
|
||||
}
|
||||
|
||||
internal void SendInnerRoute(long routeId, Type messageType, APackInfo packInfo)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
Log.Error($"SendInnerRoute routeId == 0");
|
||||
return;
|
||||
}
|
||||
|
||||
Scene.GetSession(routeId).Send(0, routeId, messageType, packInfo);
|
||||
}
|
||||
|
||||
public void SendInnerRoute(ICollection<long> routeIdCollection, IRouteMessage message)
|
||||
{
|
||||
if (routeIdCollection.Count <= 0)
|
||||
{
|
||||
Log.Error("SendInnerRoute routeIdCollection.Count <= 0");
|
||||
return;
|
||||
}
|
||||
|
||||
using var processPackInfo = ProcessPackInfo.Create(Scene, message, routeIdCollection.Count);
|
||||
foreach (var routeId in routeIdCollection)
|
||||
{
|
||||
processPackInfo.Set(0, routeId);
|
||||
Scene.GetSession(routeId).Send(processPackInfo, 0, routeId);
|
||||
}
|
||||
}
|
||||
|
||||
public async FTask SendAddressable(long addressableId, IRouteMessage message)
|
||||
{
|
||||
await CallAddressable(addressableId, message);
|
||||
}
|
||||
|
||||
internal async FTask<IResponse> CallInnerRoute(long routeId, Type requestType, APackInfo packInfo)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
Log.Error($"CallInnerRoute routeId == 0");
|
||||
return null;
|
||||
}
|
||||
|
||||
var rpcId = ++_rpcId;
|
||||
var session = Scene.GetSession(routeId);
|
||||
var requestCallback = FTask<IResponse>.Create(false);
|
||||
RequestCallback.Add(rpcId, MessageSender.Create(rpcId, requestType, requestCallback));
|
||||
session.Send(rpcId, routeId, requestType, packInfo);
|
||||
return await requestCallback;
|
||||
}
|
||||
|
||||
public async FTask<IResponse> CallInnerRouteBySession(Session session, long routeId, IRouteMessage request)
|
||||
{
|
||||
var rpcId = ++_rpcId;
|
||||
var requestCallback = FTask<IResponse>.Create(false);
|
||||
RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback));
|
||||
session.Send(request, rpcId, routeId);
|
||||
return await requestCallback;
|
||||
}
|
||||
|
||||
public async FTask<IResponse> CallInnerRoute(long routeId, IRouteMessage request)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
Log.Error($"CallInnerRoute routeId == 0");
|
||||
return null;
|
||||
}
|
||||
|
||||
var rpcId = ++_rpcId;
|
||||
var session = Scene.GetSession(routeId);
|
||||
var requestCallback = FTask<IResponse>.Create(false);
|
||||
RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback));
|
||||
session.Send(request, rpcId, routeId);
|
||||
return await requestCallback;
|
||||
}
|
||||
|
||||
public async FTask<IResponse> CallAddressable(long addressableId, IRouteMessage request)
|
||||
{
|
||||
var failCount = 0;
|
||||
|
||||
using (await AddressableRouteMessageLock.Wait(addressableId, "CallAddressable"))
|
||||
{
|
||||
var addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, addressableId);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (addressableRouteId == 0)
|
||||
{
|
||||
addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, addressableId);
|
||||
}
|
||||
|
||||
if (addressableRouteId == 0)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute);
|
||||
}
|
||||
|
||||
var iRouteResponse = await CallInnerRoute(addressableRouteId, request);
|
||||
|
||||
switch (iRouteResponse.ErrorCode)
|
||||
{
|
||||
case InnerErrorCode.ErrNotFoundRoute:
|
||||
{
|
||||
if (++failCount > 20)
|
||||
{
|
||||
Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {addressableRouteId} AddressableMessageComponent:{addressableId}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
await TimerComponent.Net.WaitAsync(500);
|
||||
addressableRouteId = 0;
|
||||
continue;
|
||||
}
|
||||
case InnerErrorCode.ErrRouteTimeout:
|
||||
{
|
||||
Log.Error($"CallAddressableRoute ErrorCode.ErrRouteTimeout Error:{iRouteResponse.ErrorCode} Message:{request}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResponseHandler(uint rpcId, IResponse response)
|
||||
{
|
||||
if (!RequestCallback.Remove(rpcId, out var routeMessageSender))
|
||||
{
|
||||
throw new Exception($"not found rpc, response.RpcId:{rpcId} response message: {response.GetType().Name} Process:{Scene.Process.Id} Scene:{Scene.SceneConfigId}");
|
||||
}
|
||||
|
||||
ResponseHandler(routeMessageSender, response);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResponseHandler(MessageSender messageSender, IResponse response)
|
||||
{
|
||||
if (response.ErrorCode == InnerErrorCode.ErrRouteTimeout)
|
||||
{
|
||||
#if FANTASY_DEVELOP
|
||||
messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注意RouteId消息超时,请注意查看是否死锁或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request.ToJson()}, response: {response}"));
|
||||
#else
|
||||
messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注意RouteId消息超时,请注意查看是否死锁或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request}, response: {response}"));
|
||||
#endif
|
||||
messageSender.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
messageSender.Tcs.SetResult(response);
|
||||
messageSender.Dispose();
|
||||
}
|
||||
|
||||
public void ReturnMessageSender(uint rpcId, MessageSender messageSender)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (messageSender.Request)
|
||||
{
|
||||
case IRouteMessage iRouteMessage:
|
||||
{
|
||||
// IRouteMessage是个特殊的RPC协议、这里不处理就可以了。
|
||||
break;
|
||||
}
|
||||
case IRequest iRequest:
|
||||
{
|
||||
var response = MessageDispatcherComponent.CreateResponse(iRequest.GetType(), InnerErrorCode.ErrRpcFail);
|
||||
var responseRpcId = messageSender.RpcId;
|
||||
ResponseHandler(responseRpcId, response);
|
||||
Log.Warning($"timeout rpcId:{rpcId} responseRpcId:{responseRpcId} {iRequest.ToJson()}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Log.Error(messageSender.Request != null
|
||||
? $"Unsupported protocol type {messageSender.Request.GetType()} rpcId:{rpcId} messageSender.Request != null"
|
||||
: $"Unsupported protocol type:{messageSender.MessageType.FullName} rpcId:{rpcId}");
|
||||
RequestCallback.Remove(rpcId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Timer;
|
||||
|
||||
#if FANTASY_NET
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络消息更新检查超时。
|
||||
/// </summary>
|
||||
public sealed class OnNetworkMessageUpdateCheckTimeout : TimerHandler<NetworkMessageUpdate>
|
||||
{
|
||||
/// <summary>
|
||||
/// 超时时间(毫秒)。
|
||||
/// </summary>
|
||||
private const long Timeout = 40000;
|
||||
|
||||
/// <summary>
|
||||
/// 处理网络消息更新检查超时。
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
protected override void Handler(NetworkMessageUpdate self)
|
||||
{
|
||||
var timeNow = TimeHelper.Now;
|
||||
var selfNetworkMessagingComponent = self.NetworkMessagingComponent;
|
||||
|
||||
// 遍历请求回调字典,检查是否有超时的请求,将超时请求添加到超时消息发送列表中。
|
||||
|
||||
foreach (var (rpcId, value) in selfNetworkMessagingComponent.RequestCallback)
|
||||
{
|
||||
if (timeNow < value.CreateTime + Timeout)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Add(rpcId, value);
|
||||
}
|
||||
|
||||
// 如果没有超时的请求,直接返回。
|
||||
|
||||
if (selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理超时的请求,根据请求类型生成相应的响应消息,并进行处理。
|
||||
|
||||
foreach (var (rpcId, routeMessageSender) in selfNetworkMessagingComponent.TimeoutRouteMessageSenders)
|
||||
{
|
||||
selfNetworkMessagingComponent.ReturnMessageSender(rpcId, routeMessageSender);
|
||||
}
|
||||
|
||||
// 清空超时消息发送列表。
|
||||
|
||||
selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
#if FANTASY_NET
|
||||
using System.Text;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Roaming;
|
||||
#endif
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Fantasy.Scheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供了一个机制来调度和处理外部网络消息。
|
||||
/// </summary>
|
||||
#if FANTASY_UNITY
|
||||
public sealed class OuterMessageScheduler : ANetworkMessageScheduler
|
||||
{
|
||||
public OuterMessageScheduler(Scene scene) : base(scene) { }
|
||||
|
||||
/// <summary>
|
||||
/// 在Unity环境下,处理外部消息的方法。
|
||||
/// </summary>
|
||||
/// <param name="session">网络会话。</param>
|
||||
/// <param name="packInfo">消息封包信息。</param>
|
||||
public override FTask Scheduler(Session session, APackInfo packInfo)
|
||||
{
|
||||
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if FANTASY_NET
|
||||
internal sealed class OuterMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene)
|
||||
{
|
||||
private readonly PingResponse _pingResponse = new PingResponse();
|
||||
public override async FTask Scheduler(Session session, APackInfo packInfo)
|
||||
{
|
||||
if (session.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packInfo.OpCodeIdStruct.Protocol)
|
||||
{
|
||||
case OpCodeType.OuterPingRequest:
|
||||
{
|
||||
// 注意心跳目前只有外网才才会有、内网之间不需要心跳。
|
||||
|
||||
session.LastReceiveTime = TimeHelper.Now;
|
||||
_pingResponse.Now = session.LastReceiveTime;
|
||||
|
||||
using (packInfo)
|
||||
{
|
||||
session.Send(_pingResponse, packInfo.RpcId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterMessage:
|
||||
case OpCodeType.OuterRequest:
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
try
|
||||
{
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"ANetworkMessageScheduler OuterResponse error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterResponse:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
NetworkMessagingComponent.ResponseHandler(packInfo.RpcId, (IResponse)packInfo.Deserialize(messageType));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
{
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var addressableRouteComponent = session.AddressableRouteComponent;
|
||||
|
||||
if (addressableRouteComponent == null)
|
||||
{
|
||||
throw new Exception("OuterMessageScheduler error session does not have an AddressableRouteComponent component");
|
||||
}
|
||||
|
||||
await addressableRouteComponent.Send(messageType, packInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterAddressableRequest:
|
||||
{
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var addressableRouteComponent = session.AddressableRouteComponent;
|
||||
|
||||
if (addressableRouteComponent == null)
|
||||
{
|
||||
throw new Exception("OuterMessageScheduler error session does not have an AddressableRouteComponent component");
|
||||
}
|
||||
|
||||
var rpcId = packInfo.RpcId;
|
||||
var runtimeId = session.RuntimeId;
|
||||
var response = await addressableRouteComponent.Call(messageType, packInfo);
|
||||
// session可能已经断开了,所以这里需要判断
|
||||
if (session.RuntimeId == runtimeId)
|
||||
{
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterCustomRouteMessage:
|
||||
{
|
||||
var packInfoProtocolCode = packInfo.ProtocolCode;
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var routeComponent = session.RouteComponent;
|
||||
|
||||
if (routeComponent == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler CustomRouteType session does not have an routeComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
if (!routeComponent.TryGetRouteId(routeType, out var routeId))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}");
|
||||
}
|
||||
|
||||
NetworkMessagingComponent.SendInnerRoute(routeId, messageType, packInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterCustomRouteRequest:
|
||||
{
|
||||
var packInfoProtocolCode = packInfo.ProtocolCode;
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var routeComponent = session.RouteComponent;
|
||||
|
||||
if (routeComponent == null)
|
||||
{
|
||||
throw new Exception("OuterMessageScheduler CustomRouteType session does not have an routeComponent component");
|
||||
}
|
||||
|
||||
if (!routeComponent.TryGetRouteId(routeType, out var routeId))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}");
|
||||
}
|
||||
|
||||
var rpcId = packInfo.RpcId;
|
||||
var runtimeId = session.RuntimeId;
|
||||
var response = await NetworkMessagingComponent.CallInnerRoute(routeId, messageType, packInfo);
|
||||
// session可能已经断开了,所以这里需要判断
|
||||
if (session.RuntimeId == runtimeId)
|
||||
{
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterRoamingMessage:
|
||||
{
|
||||
var packInfoProtocolCode = packInfo.ProtocolCode;
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var sessionRoamingComponent = session.SessionRoamingComponent;
|
||||
|
||||
if (sessionRoamingComponent == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler Roaming session does not have an sessionRoamingComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
await sessionRoamingComponent.Send(routeType, messageType, packInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterRoamingRequest:
|
||||
{
|
||||
var packInfoProtocolCode = packInfo.ProtocolCode;
|
||||
var packInfoPackInfoId = packInfo.PackInfoId;
|
||||
|
||||
try
|
||||
{
|
||||
if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType))
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode);
|
||||
|
||||
if (messageType == null)
|
||||
{
|
||||
throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}");
|
||||
}
|
||||
|
||||
var sessionRoamingComponent = session.SessionRoamingComponent;
|
||||
|
||||
if (sessionRoamingComponent == null)
|
||||
{
|
||||
throw new Exception("OuterMessageScheduler Roaming session does not have an sessionRoamingComponent component");
|
||||
}
|
||||
|
||||
var rpcId = packInfo.RpcId;
|
||||
var runtimeId = session.RuntimeId;
|
||||
var response = await sessionRoamingComponent.Call(routeType, messageType, packInfo);
|
||||
// session可能已经断开了,所以这里需要判断
|
||||
if (session.RuntimeId == runtimeId)
|
||||
{
|
||||
session.Send(response, rpcId);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (packInfo.PackInfoId == packInfoPackInfoId)
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var ipAddress = session.IsDisposed ? "null" : session.RemoteEndPoint.ToString();
|
||||
packInfo.Dispose();
|
||||
throw new NotSupportedException($"OuterMessageScheduler Received unsupported message protocolCode:{packInfo.ProtocolCode}\n1、请检查该协议所在的程序集是否在框架初始化的时候添加到框架中。\n2、如果看到这个消息表示你有可能用的老版本的导出工具,请更换为最新的导出工具。\n IP地址:{ipAddress}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 在扫描过程中发生的异常。
|
||||
/// </summary>
|
||||
public class ScanException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="ScanException"/> 类的新实例。
|
||||
/// </summary>
|
||||
public ScanException() { }
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的错误消息初始化 <see cref="ScanException"/> 类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="msg">错误消息。</param>
|
||||
public ScanException(string msg) : base(msg) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#if FANTASY_NET
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using Fantasy.Assembly;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
|
||||
namespace Fantasy.Network.HTTP
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP服务器
|
||||
/// </summary>
|
||||
public sealed class HTTPServerNetwork : ANetwork
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化入口
|
||||
/// </summary>
|
||||
/// <param name="networkTarget"></param>
|
||||
/// <param name="bindIp"></param>
|
||||
/// <param name="port"></param>
|
||||
public void Initialize(NetworkTarget networkTarget, string bindIp, int port)
|
||||
{
|
||||
base.Initialize(NetworkType.Server, NetworkProtocolType.HTTP, networkTarget);
|
||||
|
||||
try
|
||||
{
|
||||
StartAsync(bindIp, port);
|
||||
}
|
||||
catch (HttpListenerException e)
|
||||
{
|
||||
if (e.ErrorCode == 5)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("CMD管理员中输入下面其中一个命令,具体根据您是HTTPS或HTTP决定:");
|
||||
sb.AppendLine($"HTTP请输入如下:netsh http add urlacl url=http://{bindIp}:{port}/ user=Everyone");
|
||||
sb.AppendLine($"HTTPS请输入如下:netsh http add urlacl url=https://{bindIp}:{port}/ user=Everyone");
|
||||
throw new Exception(sb.ToString(), e);
|
||||
}
|
||||
|
||||
Log.Error(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAsync(string bindIp, int port)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
// 配置日志级别为 Warning 或更高
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddConsole();
|
||||
builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
||||
// 将Scene注册到 DI 容器中,传递给控制器
|
||||
builder.Services.AddSingleton(Scene);
|
||||
// 注册Scene同步过滤器
|
||||
builder.Services.AddScoped<SceneContextFilter>();
|
||||
// 注册控制器服务
|
||||
var addControllers = builder.Services.AddControllers()
|
||||
.AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });
|
||||
foreach (var assembly in AssemblySystem.ForEachAssembly)
|
||||
{
|
||||
addControllers.AddApplicationPart(assembly);
|
||||
}
|
||||
var listenUrl = "";
|
||||
var app = builder.Build();
|
||||
// 检测当前路径下是否有证书文件
|
||||
var certificatePath = Path.Combine(AppContext.BaseDirectory, $"certificate{bindIp}{port}");
|
||||
if (Directory.Exists(certificatePath))
|
||||
{
|
||||
// 加载包含证书链的 PEM 文件
|
||||
var pemCertChain = File.ReadAllText(Path.Combine(certificatePath, "chain.pem"));
|
||||
var pemPrivateKey = File.ReadAllText(Path.Combine(certificatePath, "private-key.pem"));
|
||||
// 配置 HTTPS 监听并使用证书
|
||||
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
||||
{
|
||||
kestrelServerOptions.ConfigureHttpsDefaults(https =>
|
||||
{
|
||||
https.ServerCertificate = X509Certificate2.CreateFromPem(pemCertChain, pemPrivateKey);
|
||||
});
|
||||
});
|
||||
listenUrl = $"https://{bindIp}:{port}/";
|
||||
app.Urls.Add(listenUrl);
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不安全的HTTP地址
|
||||
listenUrl = $"http://{bindIp}:{port}/";
|
||||
app.Urls.Add(listenUrl);
|
||||
}
|
||||
// 启用开发者工具
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
// 路由注册
|
||||
app.MapControllers();
|
||||
// 开启监听
|
||||
app.RunAsync();
|
||||
Log.Info($"SceneConfigId = {Scene.SceneConfigId} HTTPServer Listen {listenUrl}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除Channel
|
||||
/// </summary>
|
||||
/// <param name="channelId"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Fantasy.Network.HTTP;
|
||||
|
||||
/// <summary>
|
||||
/// 让所有实现SceneContextFilter的控制器,都在执行的Scene下执行
|
||||
/// </summary>
|
||||
public sealed class SceneContextFilter : IAsyncActionFilter
|
||||
{
|
||||
private readonly Scene _scene;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
public SceneContextFilter(Scene scene)
|
||||
{
|
||||
_scene = scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnActionExecutionAsync
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
var tcs = FTask.Create();
|
||||
|
||||
_scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
Action().Coroutine();
|
||||
});
|
||||
|
||||
await tcs;
|
||||
return;
|
||||
|
||||
async FTask Action()
|
||||
{
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
finally
|
||||
{
|
||||
tcs.SetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象客户端网络基类。
|
||||
/// </summary>
|
||||
public abstract class AClientNetwork : ANetwork, INetworkChannel
|
||||
{
|
||||
protected bool IsInit;
|
||||
public Session Session { get; protected set; }
|
||||
public abstract Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000);
|
||||
public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message);
|
||||
public override void Dispose()
|
||||
{
|
||||
IsInit = false;
|
||||
|
||||
if (Session != null)
|
||||
{
|
||||
if (!Session.IsDisposed)
|
||||
{
|
||||
Session.Dispose();
|
||||
}
|
||||
|
||||
Session = null;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Scheduler;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象网络基类。
|
||||
/// </summary>
|
||||
public abstract class ANetwork : Entity
|
||||
{
|
||||
private long _outerPackInfoId;
|
||||
private Queue<OuterPackInfo> _outerPackInfoPool;
|
||||
public readonly MemoryStreamBufferPool MemoryStreamBufferPool = new MemoryStreamBufferPool();
|
||||
|
||||
public NetworkType NetworkType { get; private set; }
|
||||
public NetworkTarget NetworkTarget { get; private set; }
|
||||
public NetworkProtocolType NetworkProtocolType { get; private set; }
|
||||
public ANetworkMessageScheduler NetworkMessageScheduler { get; private set; }
|
||||
|
||||
protected void Initialize(NetworkType networkType, NetworkProtocolType networkProtocolType, NetworkTarget networkTarget)
|
||||
{
|
||||
NetworkType = networkType;
|
||||
NetworkTarget = networkTarget;
|
||||
NetworkProtocolType = networkProtocolType;
|
||||
#if FANTASY_NET
|
||||
if (networkProtocolType == NetworkProtocolType.HTTP)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (networkTarget == NetworkTarget.Inner)
|
||||
{
|
||||
_innerPackInfoPool = new Queue<InnerPackInfo>();
|
||||
NetworkMessageScheduler = new InnerMessageScheduler(Scene);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
switch (networkType)
|
||||
{
|
||||
case NetworkType.Client:
|
||||
{
|
||||
_outerPackInfoPool = new Queue<OuterPackInfo>();
|
||||
NetworkMessageScheduler = new ClientMessageScheduler(Scene);
|
||||
break;
|
||||
}
|
||||
#if FANTASY_NET
|
||||
case NetworkType.Server:
|
||||
{
|
||||
_outerPackInfoPool = new Queue<OuterPackInfo>();
|
||||
NetworkMessageScheduler = new OuterMessageScheduler(Scene);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void RemoveChannel(uint channelId);
|
||||
public OuterPackInfo RentOuterPackInfo()
|
||||
{
|
||||
if (_outerPackInfoPool.Count == 0)
|
||||
{
|
||||
return new OuterPackInfo()
|
||||
{
|
||||
PackInfoId = ++_outerPackInfoId
|
||||
};
|
||||
}
|
||||
|
||||
if (!_outerPackInfoPool.TryDequeue(out var outerPackInfo))
|
||||
{
|
||||
return new OuterPackInfo()
|
||||
{
|
||||
PackInfoId = ++_outerPackInfoId
|
||||
};
|
||||
}
|
||||
|
||||
outerPackInfo.PackInfoId = ++_outerPackInfoId;
|
||||
return outerPackInfo;
|
||||
}
|
||||
|
||||
public void ReturnOuterPackInfo(OuterPackInfo outerPackInfo)
|
||||
{
|
||||
if (_outerPackInfoPool.Count > 512)
|
||||
{
|
||||
// 池子里最多缓存256个、其实这样设置有点多了、其实用不了512个。
|
||||
// 反而设置越大内存会占用越多。
|
||||
return;
|
||||
}
|
||||
|
||||
_outerPackInfoPool.Enqueue(outerPackInfo);
|
||||
}
|
||||
#if FANTASY_NET
|
||||
private long _innerPackInfoId;
|
||||
private Queue<InnerPackInfo> _innerPackInfoPool;
|
||||
public InnerPackInfo RentInnerPackInfo()
|
||||
{
|
||||
if (_innerPackInfoPool.Count == 0)
|
||||
{
|
||||
return new InnerPackInfo()
|
||||
{
|
||||
PackInfoId = ++_innerPackInfoId
|
||||
};
|
||||
}
|
||||
|
||||
if (!_innerPackInfoPool.TryDequeue(out var innerPackInfo))
|
||||
{
|
||||
return new InnerPackInfo()
|
||||
{
|
||||
PackInfoId = ++_innerPackInfoId
|
||||
};
|
||||
}
|
||||
|
||||
innerPackInfo.PackInfoId = ++_innerPackInfoId;
|
||||
return innerPackInfo;
|
||||
}
|
||||
|
||||
public void ReturnInnerPackInfo(InnerPackInfo innerPackInfo)
|
||||
{
|
||||
if (_innerPackInfoPool.Count > 256)
|
||||
{
|
||||
// 池子里最多缓存256个、其实这样设置有点多了、其实用不了256个。
|
||||
// 反而设置越大内存会占用越多。
|
||||
return;
|
||||
}
|
||||
|
||||
_innerPackInfoPool.Enqueue(innerPackInfo);
|
||||
}
|
||||
#endif
|
||||
public override void Dispose()
|
||||
{
|
||||
NetworkType = NetworkType.None;
|
||||
NetworkTarget = NetworkTarget.None;
|
||||
NetworkProtocolType = NetworkProtocolType.None;
|
||||
MemoryStreamBufferPool.Dispose();
|
||||
_outerPackInfoPool?.Clear();
|
||||
#if FANTASY_NET
|
||||
_innerPackInfoPool?.Clear();
|
||||
#endif
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#if FANTASY_NET
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
public abstract class ANetworkServerChannel : INetworkChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取通道的唯一标识 ID。
|
||||
/// </summary>
|
||||
public readonly uint Id;
|
||||
/// <summary>
|
||||
/// 获取通道的远程终端点。
|
||||
/// </summary>
|
||||
public readonly EndPoint RemoteEndPoint;
|
||||
/// <summary>
|
||||
/// 获取或设置通道所属的场景。
|
||||
/// </summary>
|
||||
public Scene Scene { get; protected set; }
|
||||
/// <summary>
|
||||
/// 获取或设置通道所属的会话。
|
||||
/// </summary>
|
||||
public Session Session { get; protected set; }
|
||||
/// <summary>
|
||||
/// 获取通道是否已经被释放。
|
||||
/// </summary>
|
||||
public bool IsDisposed { get; protected set; }
|
||||
|
||||
protected ANetworkServerChannel(ANetwork network, uint id, EndPoint remoteEndPoint)
|
||||
{
|
||||
Id = id;
|
||||
Scene = network.Scene;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
Session = Session.Create(network.NetworkMessageScheduler, this, network.NetworkTarget);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
if (!Session.IsDisposed)
|
||||
{
|
||||
Session.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Network.Interface
|
||||
{
|
||||
public interface INetworkChannel : IDisposable
|
||||
{
|
||||
public Session Session { get;}
|
||||
public bool IsDisposed { get;}
|
||||
public void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using kcp;
|
||||
using static kcp.KCP;
|
||||
|
||||
#pragma warning disable CS8601
|
||||
#pragma warning disable CS8602
|
||||
#pragma warning disable CS8625
|
||||
|
||||
// ReSharper disable ALL
|
||||
|
||||
namespace KCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Kcp callback
|
||||
/// </summary>
|
||||
/// <param name="buffer">KCP output destination</param>
|
||||
/// <param name="length">KCP output size (excluding reserved)</param>
|
||||
public delegate void KcpCallback(byte[] buffer, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Kcp
|
||||
/// </summary>
|
||||
public sealed unsafe class Kcp : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Kcp
|
||||
/// </summary>
|
||||
private IKCPCB* _kcp;
|
||||
|
||||
/// <summary>
|
||||
/// Output function
|
||||
/// </summary>
|
||||
private KcpCallback _output;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer
|
||||
/// </summary>
|
||||
private byte[] _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved overhead
|
||||
/// </summary>
|
||||
private int _reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Disposed
|
||||
/// </summary>
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Structure
|
||||
/// </summary>
|
||||
/// <param name="conv">ConversationId</param>
|
||||
/// <param name="output">Output</param>
|
||||
/// <param name="reserved">Reserved overhead</param>
|
||||
public Kcp(uint conv, KcpCallback output, int reserved)
|
||||
{
|
||||
_kcp = ikcp_create(conv, reserved, ref _buffer);
|
||||
_output = output;
|
||||
_reserved = reserved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set
|
||||
/// </summary>
|
||||
public bool IsSet => _kcp != null;
|
||||
|
||||
/// <summary>
|
||||
/// Conversation id
|
||||
/// </summary>
|
||||
public uint ConversationId => _kcp->conv;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum transmission unit
|
||||
/// </summary>
|
||||
public uint MaximumTransmissionUnit => _kcp->mtu;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum segment size
|
||||
/// </summary>
|
||||
public uint MaximumSegmentSize => _kcp->mss;
|
||||
|
||||
/// <summary>
|
||||
/// Connection state
|
||||
/// </summary>
|
||||
public uint State => _kcp->state;
|
||||
|
||||
/// <summary>
|
||||
/// The sequence number of the first unacknowledged packet
|
||||
/// </summary>
|
||||
public uint SendUna => _kcp->snd_una;
|
||||
|
||||
/// <summary>
|
||||
/// The sequence number for the next packet to be sent
|
||||
/// </summary>
|
||||
public uint SendNext => _kcp->snd_nxt;
|
||||
|
||||
/// <summary>
|
||||
/// The sequence number for the next packet expected to be received
|
||||
/// </summary>
|
||||
public uint ReceiveNext => _kcp->rcv_nxt;
|
||||
|
||||
/// <summary>
|
||||
/// Slow start threshold for congestion control
|
||||
/// </summary>
|
||||
public uint SlowStartThreshold => _kcp->ssthresh;
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip time variance
|
||||
/// </summary>
|
||||
public int RxRttval => _kcp->rx_rttval;
|
||||
|
||||
/// <summary>
|
||||
/// Smoothed round-trip time
|
||||
/// </summary>
|
||||
public int RxSrtt => _kcp->rx_srtt;
|
||||
|
||||
/// <summary>
|
||||
/// Retransmission timeout
|
||||
/// </summary>
|
||||
public int RxRto => _kcp->rx_rto;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum retransmission timeout
|
||||
/// </summary>
|
||||
public int RxMinrto => _kcp->rx_minrto;
|
||||
|
||||
/// <summary>
|
||||
/// Send window size
|
||||
/// </summary>
|
||||
public uint SendWindowSize => _kcp->snd_wnd;
|
||||
|
||||
/// <summary>
|
||||
/// Receive window size
|
||||
/// </summary>
|
||||
public uint ReceiveWindowSize => _kcp->rcv_wnd;
|
||||
|
||||
/// <summary>
|
||||
/// Remote window size
|
||||
/// </summary>
|
||||
public uint RemoteWindowSize => _kcp->rmt_wnd;
|
||||
|
||||
/// <summary>
|
||||
/// Congestion window size
|
||||
/// </summary>
|
||||
public uint CongestionWindowSize => _kcp->cwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Probe variable for fast recovery
|
||||
/// </summary>
|
||||
public uint Probe => _kcp->probe;
|
||||
|
||||
/// <summary>
|
||||
/// Current timestamp
|
||||
/// </summary>
|
||||
public uint Current => _kcp->current;
|
||||
|
||||
/// <summary>
|
||||
/// Flush interval
|
||||
/// </summary>
|
||||
public uint Interval => _kcp->interval;
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp for the next flush
|
||||
/// </summary>
|
||||
public uint TimestampFlush => _kcp->ts_flush;
|
||||
|
||||
/// <summary>
|
||||
/// Number of retransmissions
|
||||
/// </summary>
|
||||
public uint Transmissions => _kcp->xmit;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets in the receive buffer
|
||||
/// </summary>
|
||||
public uint ReceiveBufferCount => _kcp->nrcv_buf;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets in the receive queue
|
||||
/// </summary>
|
||||
public uint ReceiveQueueCount => _kcp->nrcv_que;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets wait to receive
|
||||
/// </summary>
|
||||
public uint WaitReceiveCount => _kcp->nrcv_buf + _kcp->nrcv_que;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets in the send buffer
|
||||
/// </summary>
|
||||
public uint SendBufferCount => _kcp->nsnd_buf;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets in the send queue
|
||||
/// </summary>
|
||||
public uint SendQueueCount => _kcp->nsnd_que;
|
||||
|
||||
/// <summary>
|
||||
/// Number of packets wait to send
|
||||
/// </summary>
|
||||
public uint WaitSendCount => _kcp->nsnd_buf + _kcp->nsnd_que;
|
||||
|
||||
/// <summary>
|
||||
/// Whether Nagle's algorithm is disabled
|
||||
/// </summary>
|
||||
public uint NoDelay => _kcp->nodelay;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the KCP connection has been updated
|
||||
/// </summary>
|
||||
public uint Updated => _kcp->updated;
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp for the next probe
|
||||
/// </summary>
|
||||
public uint TimestampProbe => _kcp->ts_probe;
|
||||
|
||||
/// <summary>
|
||||
/// Probe wait time
|
||||
/// </summary>
|
||||
public uint ProbeWait => _kcp->probe_wait;
|
||||
|
||||
/// <summary>
|
||||
/// Incremental increase
|
||||
/// </summary>
|
||||
public uint Increment => _kcp->incr;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the acknowledge list
|
||||
/// </summary>
|
||||
public uint* AckList => _kcp->acklist;
|
||||
|
||||
/// <summary>
|
||||
/// Count of acknowledges
|
||||
/// </summary>
|
||||
public uint AckCount => _kcp->ackcount;
|
||||
|
||||
/// <summary>
|
||||
/// Number of acknowledge blocks
|
||||
/// </summary>
|
||||
public uint AckBlock => _kcp->ackblock;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer
|
||||
/// </summary>
|
||||
public byte[] Buffer => _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Fast resend trigger count
|
||||
/// </summary>
|
||||
public int FastResend => _kcp->fastresend;
|
||||
|
||||
/// <summary>
|
||||
/// Fast resend limit
|
||||
/// </summary>
|
||||
public int FastResendLimit => _kcp->fastlimit;
|
||||
|
||||
/// <summary>
|
||||
/// Whether congestion control is disabled
|
||||
/// </summary>
|
||||
public int NoCongestionWindow => _kcp->nocwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Whether stream mode is enabled
|
||||
/// </summary>
|
||||
public int StreamMode => _kcp->stream;
|
||||
|
||||
/// <summary>
|
||||
/// Output function pointer
|
||||
/// </summary>
|
||||
public KcpCallback Output => _output;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved overhead
|
||||
/// </summary>
|
||||
public int Reserved => _reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
|
||||
return;
|
||||
ikcp_release(_kcp);
|
||||
_kcp = null;
|
||||
_output = null;
|
||||
_buffer = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set output
|
||||
/// </summary>
|
||||
/// <param name="output">Output</param>
|
||||
public void SetOutput(KcpCallback output) => _output = output;
|
||||
|
||||
/// <summary>
|
||||
/// Destructure
|
||||
/// </summary>
|
||||
~Kcp() => Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Send
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <returns>Sent bytes</returns>
|
||||
public int Send(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
return ikcp_send(_kcp, pinnedBuffer, buffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <param name="length">Length</param>
|
||||
/// <returns>Sent bytes</returns>
|
||||
public int Send(byte* buffer, int length) => ikcp_send(_kcp, buffer, length);
|
||||
|
||||
/// <summary>
|
||||
/// Input
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <returns>Input bytes</returns>
|
||||
public int Input(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
return ikcp_input(_kcp, pinnedBuffer, buffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <param name="length">Length</param>
|
||||
/// <returns>Input bytes</returns>
|
||||
public int Input(byte* buffer, int length) => ikcp_input(_kcp, buffer, length);
|
||||
|
||||
/// <summary>
|
||||
/// Peek size
|
||||
/// </summary>
|
||||
/// <returns>Peeked size</returns>
|
||||
public int PeekSize() => ikcp_peeksize(_kcp);
|
||||
|
||||
/// <summary>
|
||||
/// Receive
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <returns>Received bytes</returns>
|
||||
public int Receive(Span<byte> buffer)
|
||||
{
|
||||
fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
return ikcp_recv(_kcp, pinnedBuffer, buffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receive
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer</param>
|
||||
/// <param name="length">Length</param>
|
||||
/// <returns>Received bytes</returns>
|
||||
public int Receive(byte* buffer, int length) => ikcp_recv(_kcp, buffer, length);
|
||||
|
||||
/// <summary>
|
||||
/// Update
|
||||
/// </summary>
|
||||
/// <param name="current">Timestamp</param>
|
||||
public void Update(uint current)
|
||||
{
|
||||
fixed (byte* ptr = &_buffer[_reserved])
|
||||
{
|
||||
ikcp_update(_kcp, current, ptr, _buffer, _output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check
|
||||
/// </summary>
|
||||
/// <param name="current">Timestamp</param>
|
||||
/// <returns>Next flush timestamp</returns>
|
||||
public uint Check(uint current) => ikcp_check(_kcp, current);
|
||||
|
||||
/// <summary>
|
||||
/// Flush
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
fixed (byte* ptr = &_buffer[_reserved])
|
||||
{
|
||||
ikcp_flush(_kcp, ptr, _buffer, _output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set maximum transmission unit
|
||||
/// </summary>
|
||||
/// <param name="mtu">Maximum transmission unit</param>
|
||||
/// <returns>Set</returns>
|
||||
public int SetMtu(int mtu) => ikcp_setmtu(_kcp, mtu, _reserved, ref _buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Set flush interval
|
||||
/// </summary>
|
||||
/// <param name="interval">Flush interval</param>
|
||||
public void SetInterval(int interval) => ikcp_interval(_kcp, interval);
|
||||
|
||||
/// <summary>
|
||||
/// Set no delay
|
||||
/// </summary>
|
||||
/// <param name="nodelay">Whether Nagle's algorithm is disabled</param>
|
||||
/// <param name="interval">Flush interval</param>
|
||||
/// <param name="resend">Fast resend trigger count</param>
|
||||
/// <param name="nc">No congestion window</param>
|
||||
public void SetNoDelay(int nodelay, int interval, int resend, int nc) => ikcp_nodelay(_kcp, nodelay, interval, resend, nc);
|
||||
|
||||
/// <summary>
|
||||
/// Set window size
|
||||
/// </summary>
|
||||
/// <param name="sndwnd">Send window size</param>
|
||||
/// <param name="rcvwnd">Receive window size</param>
|
||||
public void SetWindowSize(int sndwnd, int rcvwnd) => ikcp_wndsize(_kcp, sndwnd, rcvwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Set fast resend limit
|
||||
/// </summary>
|
||||
/// <param name="fastlimit">Fast resend limit</param>
|
||||
public void SetFastResendLimit(int fastlimit) => _kcp->fastlimit = Math.Clamp(fastlimit, 0, 5);
|
||||
|
||||
/// <summary>
|
||||
/// Set whether stream mode is enabled
|
||||
/// </summary>
|
||||
/// <param name="stream">Whether stream mode is enabled</param>
|
||||
public void SetStreamMode(int stream) => _kcp->stream = stream == 1 ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Set minimum retransmission timeout
|
||||
/// </summary>
|
||||
/// <param name="minrto">Minimum retransmission timeout</param>
|
||||
public void SetMinrto(int minrto) => _kcp->rx_minrto = (int)Math.Clamp(minrto, 1, IKCP_RTO_MAX);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
// ReSharper disable ALL
|
||||
|
||||
namespace kcp
|
||||
{
|
||||
public static unsafe partial class KCP
|
||||
{
|
||||
public static void* malloc(nuint size)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return NativeMemory.Alloc((nuint)size);
|
||||
#else
|
||||
return (void*)Marshal.AllocHGlobal((nint)size);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void free(void* memory)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
NativeMemory.Free(memory);
|
||||
#else
|
||||
Marshal.FreeHGlobal((nint)memory);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void memcpy(void* dst, void* src, nuint size) => Unsafe.CopyBlockUnaligned(dst, src, (uint)size);
|
||||
|
||||
public static void memset(void* dst, byte val, nuint size) => Unsafe.InitBlockUnaligned(dst, val, (uint)size);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void assert(bool condition) => Debug.Assert(condition);
|
||||
|
||||
public static void abort() => Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
// ReSharper disable ALL
|
||||
|
||||
namespace kcp
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct IQUEUEHEAD
|
||||
{
|
||||
public IQUEUEHEAD* next;
|
||||
public IQUEUEHEAD* prev;
|
||||
}
|
||||
|
||||
public static unsafe partial class KCP
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void iqueue_init(IQUEUEHEAD* head)
|
||||
{
|
||||
head->next = head;
|
||||
head->prev = head;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T* iqueue_entry<T>(IQUEUEHEAD* ptr) where T : unmanaged => ((T*)(((byte*)((T*)ptr))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void iqueue_add(IQUEUEHEAD* node, IQUEUEHEAD* head)
|
||||
{
|
||||
node->prev = head;
|
||||
node->next = head->next;
|
||||
head->next->prev = node;
|
||||
head->next = node;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void iqueue_add_tail(IQUEUEHEAD* node, IQUEUEHEAD* head)
|
||||
{
|
||||
node->prev = head->prev;
|
||||
node->next = head;
|
||||
head->prev->next = node;
|
||||
head->prev = node;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void iqueue_del(IQUEUEHEAD* entry)
|
||||
{
|
||||
entry->next->prev = entry->prev;
|
||||
entry->prev->next = entry->next;
|
||||
entry->next = (IQUEUEHEAD*)0;
|
||||
entry->prev = (IQUEUEHEAD*)0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void iqueue_del_init(IQUEUEHEAD* entry)
|
||||
{
|
||||
iqueue_del(entry);
|
||||
iqueue_init(entry);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool iqueue_is_empty(IQUEUEHEAD* entry) => entry == entry->next;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct IKCPSEG
|
||||
{
|
||||
public IQUEUEHEAD node;
|
||||
public uint conv;
|
||||
public uint cmd;
|
||||
public uint frg;
|
||||
public uint wnd;
|
||||
public uint ts;
|
||||
public uint sn;
|
||||
public uint una;
|
||||
public uint len;
|
||||
public uint resendts;
|
||||
public uint rto;
|
||||
public uint fastack;
|
||||
public uint xmit;
|
||||
public fixed byte data[1];
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct IKCPCB
|
||||
{
|
||||
public uint conv, mtu, mss, state;
|
||||
public uint snd_una, snd_nxt, rcv_nxt;
|
||||
public uint ts_recent, ts_lastack, ssthresh;
|
||||
public int rx_rttval, rx_srtt, rx_rto, rx_minrto;
|
||||
public uint snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
|
||||
public uint current, interval, ts_flush, xmit;
|
||||
public uint nrcv_buf, nsnd_buf;
|
||||
public uint nrcv_que, nsnd_que;
|
||||
public uint nodelay, updated;
|
||||
public uint ts_probe, probe_wait;
|
||||
public uint dead_link, incr;
|
||||
public IQUEUEHEAD snd_queue;
|
||||
public IQUEUEHEAD rcv_queue;
|
||||
public IQUEUEHEAD snd_buf;
|
||||
public IQUEUEHEAD rcv_buf;
|
||||
public uint* acklist;
|
||||
public uint ackcount;
|
||||
public uint ackblock;
|
||||
public int fastresend;
|
||||
public int fastlimit;
|
||||
public int nocwnd, stream;
|
||||
}
|
||||
|
||||
public static partial class KCP
|
||||
{
|
||||
public const uint IKCP_LOG_OUTPUT = 1;
|
||||
public const uint IKCP_LOG_INPUT = 2;
|
||||
public const uint IKCP_LOG_SEND = 4;
|
||||
public const uint IKCP_LOG_RECV = 8;
|
||||
public const uint IKCP_LOG_IN_DATA = 16;
|
||||
public const uint IKCP_LOG_IN_ACK = 32;
|
||||
public const uint IKCP_LOG_IN_PROBE = 64;
|
||||
public const uint IKCP_LOG_IN_WINS = 128;
|
||||
public const uint IKCP_LOG_OUT_DATA = 256;
|
||||
public const uint IKCP_LOG_OUT_ACK = 512;
|
||||
public const uint IKCP_LOG_OUT_PROBE = 1024;
|
||||
public const uint IKCP_LOG_OUT_WINS = 2048;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,697 @@
|
||||
#if !FANTASY_WEBGL
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
using KCP;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// ReSharper disable PossibleNullReferenceException
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
namespace Fantasy.Network.KCP
|
||||
{
|
||||
public sealed class KCPClientNetworkUpdateSystem : UpdateSystem<KCPClientNetwork>
|
||||
{
|
||||
protected override void Update(KCPClientNetwork self)
|
||||
{
|
||||
self.CheckUpdate();
|
||||
}
|
||||
}
|
||||
public sealed class KCPClientNetwork : AClientNetwork
|
||||
{
|
||||
private Kcp _kcp;
|
||||
private Socket _socket;
|
||||
private int _maxSndWnd;
|
||||
private long _startTime;
|
||||
private bool _isConnected;
|
||||
private bool _isDisconnect;
|
||||
private uint _updateMinTime;
|
||||
private bool _isInnerDispose;
|
||||
private long _connectTimeoutId;
|
||||
private bool _allowWraparound = true;
|
||||
private IPEndPoint _remoteAddress;
|
||||
private BufferPacketParser _packetParser;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly byte[] _sendBuff = new byte[5];
|
||||
private readonly byte[] _receiveBuffer = new byte[Packet.PacketBodyMaxLength + 20];
|
||||
private readonly List<uint> _updateTimeOutTime = new List<uint>();
|
||||
private readonly SortedSet<uint> _updateTimer = new SortedSet<uint>();
|
||||
private readonly SocketAsyncEventArgs _connectEventArgs = new SocketAsyncEventArgs();
|
||||
private readonly Queue<MemoryStreamBuffer> _messageCache = new Queue<MemoryStreamBuffer>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
#if FANTASY_UNITY
|
||||
private readonly EndPoint _ipEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
#endif
|
||||
private event Action OnConnectFail;
|
||||
private event Action OnConnectComplete;
|
||||
private event Action OnConnectDisconnect;
|
||||
public uint ChannelId { get; private set; }
|
||||
private uint TimeNow => (uint) (TimeHelper.Now - _startTime);
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget)
|
||||
{
|
||||
base.Initialize(NetworkType.Client, NetworkProtocolType.KCP, networkTarget);
|
||||
_packetParser = PacketParserFactory.CreateClientBufferPacket(this);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isInnerDispose = true;
|
||||
|
||||
if (!_isDisconnect)
|
||||
{
|
||||
SendDisconnect();
|
||||
}
|
||||
|
||||
ClearConnectTimeout();
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
|
||||
OnConnectDisconnect?.Invoke();
|
||||
_kcp.Dispose();
|
||||
|
||||
if (_socket.Connected)
|
||||
{
|
||||
_socket.Close();
|
||||
}
|
||||
|
||||
_packetParser.Dispose();
|
||||
ChannelId = 0;
|
||||
_isConnected = false;
|
||||
_messageCache.Clear();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Connect
|
||||
|
||||
public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000)
|
||||
{
|
||||
if (IsInit)
|
||||
{
|
||||
throw new NotSupportedException($"KCPClientNetwork Has already been initialized. If you want to call Connect again, please re instantiate it.");
|
||||
}
|
||||
|
||||
IsInit = true;
|
||||
_startTime = TimeHelper.Now;
|
||||
ChannelId = CreateChannelId();
|
||||
_remoteAddress = NetworkHelper.GetIPEndPoint(remoteAddress);
|
||||
OnConnectFail = onConnectFail;
|
||||
OnConnectComplete = onConnectComplete;
|
||||
OnConnectDisconnect = onConnectDisconnect;
|
||||
_connectEventArgs.Completed += OnConnectSocketCompleted;
|
||||
_connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () =>
|
||||
{
|
||||
OnConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
_connectEventArgs.RemoteEndPoint = _remoteAddress;
|
||||
_socket = new Socket(_remoteAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
_socket.Blocking = false;
|
||||
_socket.SetSocketBufferToOsLimit();
|
||||
_socket.SetSioUdpConnReset();
|
||||
_socket.Bind(new IPEndPoint(IPAddress.Any, 0));
|
||||
_kcp = KCPFactory.Create(NetworkTarget, ChannelId, KcpSpanCallback, out var kcpSettings);
|
||||
_maxSndWnd = kcpSettings.MaxSendWindowSize;
|
||||
|
||||
if (!_socket.ConnectAsync(_connectEventArgs))
|
||||
{
|
||||
try
|
||||
{
|
||||
OnReceiveSocketComplete();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
OnConnectFail?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
Session = Session.Create(this, _remoteAddress);
|
||||
return Session;
|
||||
}
|
||||
|
||||
private void OnConnectSocketCompleted(object sender, SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (asyncEventArgs.LastOperation == SocketAsyncOperation.Connect)
|
||||
{
|
||||
if (asyncEventArgs.SocketError == SocketError.Success)
|
||||
{
|
||||
Scene.ThreadSynchronizationContext.Post(OnReceiveSocketComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceiveSocketComplete()
|
||||
{
|
||||
SendRequestConnection();
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
#if FANTASY_UNITY
|
||||
MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> arraySegment);
|
||||
var result = await _socket.ReceiveFromAsync(arraySegment, SocketFlags.None, _ipEndPoint);
|
||||
_pipe.Writer.Advance(result.ReceivedBytes);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
#else
|
||||
var result = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token);
|
||||
_pipe.Writer.Advance(result);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
#endif
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Unexpected exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var header, out var channelId, out var message))
|
||||
{
|
||||
ReceiveData(ref header, ref channelId, ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out KcpHeader header, out uint channelId, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length < 5)
|
||||
{
|
||||
channelId = 0;
|
||||
message = default;
|
||||
header = KcpHeader.None;
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
buffer = buffer.Slice(buffer.Length);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var readOnlyMemory = buffer.First;
|
||||
|
||||
if (MemoryMarshal.TryGetArray(readOnlyMemory, out var arraySegment))
|
||||
{
|
||||
fixed (byte* bytePointer = &arraySegment.Array[arraySegment.Offset])
|
||||
{
|
||||
header = (KcpHeader)bytePointer[0];
|
||||
channelId = Unsafe.ReadUnaligned<uint>(ref bytePointer[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果无法获取数组段,回退到安全代码来执行。这种情况几乎不会发生、为了保险还是写一下了。
|
||||
var firstSpan = readOnlyMemory.Span;
|
||||
header = (KcpHeader)firstSpan[0];
|
||||
channelId = MemoryMarshal.Read<uint>(firstSpan.Slice(1, 4));
|
||||
|
||||
}
|
||||
|
||||
message = readOnlyMemory.Slice(5);
|
||||
buffer = buffer.Slice(readOnlyMemory.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref KcpHeader header, ref uint channelId, ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
switch (header)
|
||||
{
|
||||
// 发送握手给服务器
|
||||
case KcpHeader.RepeatChannelId:
|
||||
{
|
||||
// 到这里是客户端的channelId再服务器上已经存在、需要重新生成一个再次尝试连接
|
||||
ChannelId = CreateChannelId();
|
||||
SendRequestConnection();
|
||||
break;
|
||||
}
|
||||
// 收到服务器发送会来的确认握手
|
||||
case KcpHeader.WaitConfirmConnection:
|
||||
{
|
||||
if (channelId != ChannelId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ClearConnectTimeout();
|
||||
SendConfirmConnection();
|
||||
OnConnectComplete?.Invoke();
|
||||
_isConnected = true;
|
||||
while (_messageCache.TryDequeue(out var memoryStream))
|
||||
{
|
||||
SendMemoryStream(memoryStream);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 收到服务器发送的消息
|
||||
case KcpHeader.ReceiveData:
|
||||
{
|
||||
if (buffer.Length == 5)
|
||||
{
|
||||
Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5");
|
||||
break;
|
||||
}
|
||||
|
||||
if (channelId != ChannelId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Input(buffer);
|
||||
break;
|
||||
}
|
||||
// 接收到服务器的断开连接消息
|
||||
case KcpHeader.Disconnect:
|
||||
{
|
||||
if (channelId != ChannelId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_isDisconnect = true;
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Input(ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
_kcp.Input(buffer.Span);
|
||||
AddToUpdate(0);
|
||||
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var peekSize = _kcp.PeekSize();
|
||||
|
||||
if (peekSize < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var receiveCount = _kcp.Receive(_receiveBuffer.AsSpan(0, peekSize));
|
||||
|
||||
if (receiveCount != peekSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_packetParser.UnPack(_receiveBuffer, ref receiveCount, out var packInfo))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Debug($"RemoteAddress:{_remoteAddress} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
public void CheckUpdate()
|
||||
{
|
||||
var nowTime = TimeNow;
|
||||
_allowWraparound = nowTime < _updateMinTime;
|
||||
|
||||
if (IsTimeGreaterThan(nowTime, _updateMinTime) && _updateTimer.Count > 0)
|
||||
{
|
||||
foreach (var timeId in _updateTimer)
|
||||
{
|
||||
if (IsTimeGreaterThan(timeId, nowTime))
|
||||
{
|
||||
_updateMinTime = timeId;
|
||||
break;
|
||||
}
|
||||
|
||||
_updateTimeOutTime.Add(timeId);
|
||||
}
|
||||
|
||||
foreach (var timeId in _updateTimeOutTime)
|
||||
{
|
||||
_updateTimer.Remove(timeId);
|
||||
KcpUpdate();
|
||||
}
|
||||
|
||||
_updateTimeOutTime.Clear();
|
||||
}
|
||||
|
||||
_allowWraparound = true;
|
||||
}
|
||||
|
||||
private void AddToUpdate(uint tillTime)
|
||||
{
|
||||
if (tillTime == 0)
|
||||
{
|
||||
KcpUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTimeGreaterThan(_updateMinTime, tillTime) || _updateMinTime == 0)
|
||||
{
|
||||
_updateMinTime = tillTime;
|
||||
}
|
||||
|
||||
_updateTimer.Add(tillTime);
|
||||
}
|
||||
|
||||
private void KcpUpdate()
|
||||
{
|
||||
var nowTime = TimeNow;
|
||||
|
||||
try
|
||||
{
|
||||
_kcp.Update(nowTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
AddToUpdate(_kcp.Check(nowTime));
|
||||
}
|
||||
|
||||
private const uint HalfMaxUint = uint.MaxValue / 2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsTimeGreaterThan(uint timeId, uint nowTime)
|
||||
{
|
||||
if (!_allowWraparound)
|
||||
{
|
||||
return timeId > nowTime;
|
||||
}
|
||||
var diff = timeId - nowTime;
|
||||
// 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。
|
||||
// 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。
|
||||
return diff < HalfMaxUint || diff == HalfMaxUint;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect;
|
||||
private const byte KcpHeaderReceiveData = (byte)KcpHeader.ReceiveData;
|
||||
private const byte KcpHeaderRequestConnection = (byte)KcpHeader.RequestConnection;
|
||||
private const byte KcpHeaderConfirmConnection = (byte)KcpHeader.ConfirmConnection;
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message);
|
||||
|
||||
if (!_isConnected)
|
||||
{
|
||||
_messageCache.Enqueue(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
SendMemoryStream(buffer);
|
||||
}
|
||||
|
||||
private void SendMemoryStream(MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
if (_kcp.WaitSendCount > _maxSndWnd)
|
||||
{
|
||||
// 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接
|
||||
Log.Warning($"ERR_KcpWaitSendSizeTooLarge {_kcp.WaitSendCount} > {_maxSndWnd}");
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_kcp.Send(memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position));
|
||||
AddToUpdate(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void SendRequestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderRequestConnection;
|
||||
*(uint*)(p + 1) = ChannelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void SendConfirmConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderConfirmConnection;
|
||||
*(uint*)(p + 1) = ChannelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void SendDisconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderDisconnect;
|
||||
*(uint*)(p + 1) = ChannelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SendAsync(byte[] buffer, int offset, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket.Send(new ArraySegment<byte>(buffer, offset, count), SocketFlags.None);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
//Log.Error($"SocketException: {ex.Message}"); // 处理网络错误
|
||||
Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Log.Error($"ObjectDisposedException: {ex.Message}"); // 处理套接字已关闭的情况
|
||||
Dispose();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception: {ex.Message}"); // 捕获其他异常
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void KcpSpanCallback(byte[] buffer, int count)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
throw new Exception("KcpOutput count 0");
|
||||
}
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
p[0] = KcpHeaderReceiveData;
|
||||
*(uint*)(p + 1) = ChannelId;
|
||||
}
|
||||
|
||||
SendAsync(buffer, 0, count + 5);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ClearConnectTimeout()
|
||||
{
|
||||
if (_connectTimeoutId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene?.TimerComponent?.Net.Remove(ref _connectTimeoutId);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe uint CreateChannelId()
|
||||
{
|
||||
uint value;
|
||||
RandomNumberGenerator.Fill(MemoryMarshal.CreateSpan(ref *(byte*)&value, 4));
|
||||
return 0xC0000000 | (value & int.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#if !FANTASY_WEBGL
|
||||
using System;
|
||||
using KCP;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
namespace Fantasy.Network.KCP
|
||||
{
|
||||
public class KCPSettings
|
||||
{
|
||||
public int Mtu { get; private set; }
|
||||
public int SendWindowSize { get; private set; }
|
||||
public int ReceiveWindowSize { get; private set; }
|
||||
public int MaxSendWindowSize { get; private set; }
|
||||
|
||||
public static KCPSettings Create(NetworkTarget networkTarget)
|
||||
{
|
||||
var settings = new KCPSettings();
|
||||
|
||||
switch (networkTarget)
|
||||
{
|
||||
case NetworkTarget.Outer:
|
||||
{
|
||||
// 外网设置470的原因:
|
||||
// 1、mtu设置过大有可能路由器过滤掉
|
||||
// 2、降低 mtu 到 470,同样数据虽然会发更多的包,但是小包在路由层优先级更高
|
||||
settings.Mtu = 470;
|
||||
#if FANTASY_NET
|
||||
settings.SendWindowSize = 8192;
|
||||
settings.ReceiveWindowSize = 8192;
|
||||
settings.MaxSendWindowSize = 8192 * 8192 * 7;
|
||||
#endif
|
||||
#if FANTASY_UNITY || FANTASY_CONSOLE
|
||||
settings.SendWindowSize = 512;
|
||||
settings.ReceiveWindowSize = 512;
|
||||
settings.MaxSendWindowSize = 512 * 512 * 7;
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
#if FANTASY_NET
|
||||
case NetworkTarget.Inner:
|
||||
{
|
||||
// 内网设置1400的原因
|
||||
// 1、一般都是同一台服务器来运行多个进程来处理
|
||||
// 2、内网每个进程跟其他进程只有一个通道进行发送、所以发送的数量会比较大
|
||||
// 3、如果不把窗口设置大点、会出现消息滞后。
|
||||
// 4、因为内网发送的可不只是外网转发数据、还有可能是其他进程的通讯
|
||||
settings.Mtu = 1200;
|
||||
settings.SendWindowSize = 8192;
|
||||
settings.ReceiveWindowSize = 8192;
|
||||
settings.MaxSendWindowSize = 8192 * 8192 * 7;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException($"KCPServerNetwork NotSupported NetworkType:{networkTarget}");
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KCPFactory
|
||||
{
|
||||
public const int FANTASY_KCP_RESERVED_HEAD = 5;
|
||||
|
||||
public static Kcp Create(NetworkTarget networkTarget, uint conv, KcpCallback output, out KCPSettings kcpSettings)
|
||||
{
|
||||
var kcp = new Kcp(conv, output, FANTASY_KCP_RESERVED_HEAD);
|
||||
kcpSettings = KCPSettings.Create(networkTarget);
|
||||
kcp.SetNoDelay(1, 5, 2, 1);
|
||||
kcp.SetWindowSize(kcpSettings.SendWindowSize, kcpSettings.ReceiveWindowSize);
|
||||
kcp.SetMtu(kcpSettings.Mtu);
|
||||
kcp.SetMinrto(30);
|
||||
return kcp;
|
||||
}
|
||||
|
||||
public static Kcp Create(KCPSettings kcpSettings, uint conv, KcpCallback output)
|
||||
{
|
||||
var kcp = new Kcp(conv, output, FANTASY_KCP_RESERVED_HEAD);
|
||||
kcp.SetNoDelay(1, 5, 2, 1);
|
||||
kcp.SetWindowSize(kcpSettings.SendWindowSize, kcpSettings.ReceiveWindowSize);
|
||||
kcp.SetMtu(kcpSettings.Mtu);
|
||||
kcp.SetMinrto(30);
|
||||
return kcp;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Fantasy.Network.KCP
|
||||
#pragma warning disable CS1591
|
||||
{
|
||||
public enum KcpHeader : byte
|
||||
{
|
||||
None = 0x00,
|
||||
RequestConnection = 0x01,
|
||||
WaitConfirmConnection = 0x02,
|
||||
ConfirmConnection = 0x03,
|
||||
RepeatChannelId = 0x04,
|
||||
ReceiveData = 0x06,
|
||||
Disconnect = 0x07
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
// #if FANTASY_NET
|
||||
// using System.Buffers;
|
||||
// using System.Net;
|
||||
// using System.Net.Sockets;
|
||||
// using System.Runtime.CompilerServices;
|
||||
// using System.Runtime.InteropServices;
|
||||
// using Fantasy.Async;
|
||||
// using Fantasy.DataStructure.Collection;
|
||||
// using Fantasy.Entitas.Interface;
|
||||
// using Fantasy.Helper;
|
||||
// using Fantasy.Network.Interface;
|
||||
// #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
//
|
||||
// // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// #pragma warning disable CS8604 // Possible null reference argument.
|
||||
// #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
// #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
// #pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
//
|
||||
// #pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
//
|
||||
// namespace Fantasy.Network.KCP
|
||||
// {
|
||||
// public sealed class KCPServerNetworkUpdateSystem : UpdateSystem<KCPServerNetwork>
|
||||
// {
|
||||
// protected override void Update(KCPServerNetwork self)
|
||||
// {
|
||||
// self.Update();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public struct PendingConnection
|
||||
// {
|
||||
// public readonly uint ChannelId;
|
||||
// public readonly uint TimeOutId;
|
||||
// public readonly IPEndPoint RemoteEndPoint;
|
||||
//
|
||||
// public PendingConnection(uint channelId, IPEndPoint remoteEndPoint, uint time)
|
||||
// {
|
||||
// ChannelId = channelId;
|
||||
// RemoteEndPoint = remoteEndPoint;
|
||||
// TimeOutId = time + 10 * 1000; // 设置10秒超时,如果10秒内没有确认连接则删除。
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public sealed class KCPServerNetwork : ANetwork
|
||||
// {
|
||||
// private Socket _socket;
|
||||
// private Thread _thread;
|
||||
// private long _startTime;
|
||||
// private uint _updateMinTime;
|
||||
// private uint _pendingMinTime;
|
||||
// private bool _allowWraparound = true;
|
||||
//
|
||||
// private readonly byte[] _sendBuff = new byte[5];
|
||||
// private readonly List<uint> _pendingTimeOutTime = new List<uint>();
|
||||
// private readonly HashSet<uint> _updateChannels = new HashSet<uint>();
|
||||
// private readonly List<uint> _updateTimeOutTime = new List<uint>();
|
||||
// private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
// private readonly SortedOneToManyList<uint, uint> _updateTimer = new SortedOneToManyList<uint, uint>();
|
||||
//
|
||||
// private readonly Dictionary<uint, PendingConnection> _pendingConnection = new Dictionary<uint, PendingConnection>();
|
||||
// private readonly SortedOneToManyList<uint, uint> _pendingConnectionTimeOut = new SortedOneToManyList<uint, uint>();
|
||||
// private readonly Dictionary<uint, KCPServerNetworkChannel> _connectionChannel = new Dictionary<uint, KCPServerNetworkChannel>();
|
||||
//
|
||||
// public KCPSettings Settings { get; private set; }
|
||||
//
|
||||
// private uint TimeNow => (uint)(TimeHelper.Now - _startTime);
|
||||
//
|
||||
// public void Initialize(NetworkTarget networkTarget, IPEndPoint address)
|
||||
// {
|
||||
// _startTime = TimeHelper.Now;
|
||||
// Settings = KCPSettings.Create(networkTarget);
|
||||
// base.Initialize(NetworkType.Server, NetworkProtocolType.KCP, networkTarget);
|
||||
// _socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
// _socket.Blocking = false;
|
||||
// _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
|
||||
// if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
// {
|
||||
// _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
// }
|
||||
//
|
||||
// _socket.Blocking = false;
|
||||
// _socket.Bind(address);
|
||||
// _socket.SetSocketBufferToOsLimit();
|
||||
// _socket.SetSioUdpConnReset();
|
||||
// _thread = new Thread(() =>
|
||||
// {
|
||||
// ReceiveSocketAsync().Coroutine();
|
||||
// });
|
||||
// _thread.Start();
|
||||
// Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} KCPServer Listen {address}");
|
||||
// }
|
||||
//
|
||||
// public override void Dispose()
|
||||
// {
|
||||
// if (IsDisposed)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// _cancellationTokenSource.Cancel();
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// // 通常情况下,此处的异常可以忽略
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// foreach (var (_, channel) in _connectionChannel.ToArray())
|
||||
// {
|
||||
// channel.Dispose();
|
||||
// }
|
||||
//
|
||||
// _connectionChannel.Clear();
|
||||
// _pendingConnection.Clear();
|
||||
//
|
||||
// if (_socket != null)
|
||||
// {
|
||||
// _socket.Dispose();
|
||||
// _socket = null;
|
||||
// }
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// Log.Error(e);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// base.Dispose();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #region ReceiveSocket
|
||||
//
|
||||
// private const int BufferSize = 8192;
|
||||
// private static readonly ArrayPool<byte> BufferPool = ArrayPool<byte>.Shared;
|
||||
// private async FTask ReceiveSocketAsync()
|
||||
// {
|
||||
// var sceneThreadSynchronizationContext = Scene.ThreadSynchronizationContext;
|
||||
// var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
//
|
||||
// while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// IPEndPoint receiveRemoteEndPoint = null;
|
||||
// var buffer = BufferPool.Rent(BufferSize);
|
||||
// var socketReceiveFromResult = await _socket.ReceiveFromAsync(buffer, SocketFlags.None, remoteEndPoint, _cancellationTokenSource.Token);
|
||||
// var receivedBytes = socketReceiveFromResult.ReceivedBytes;
|
||||
//
|
||||
// switch (receivedBytes)
|
||||
// {
|
||||
// case 5:
|
||||
// {
|
||||
// switch ((KcpHeader)buffer[0])
|
||||
// {
|
||||
// case KcpHeader.RequestConnection:
|
||||
// case KcpHeader.ConfirmConnection:
|
||||
// {
|
||||
// receiveRemoteEndPoint = socketReceiveFromResult.RemoteEndPoint.Clone();
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case < 5:
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// sceneThreadSynchronizationContext.Post(() =>
|
||||
// {
|
||||
// ReceiveDataAsync(buffer, receivedBytes, receiveRemoteEndPoint);
|
||||
// });
|
||||
// }
|
||||
// catch (SocketException ex)
|
||||
// {
|
||||
// Log.Error($"Socket exception: {ex.Message}");
|
||||
// Dispose();
|
||||
// break;
|
||||
// }
|
||||
// catch (OperationCanceledException)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// catch (ObjectDisposedException)
|
||||
// {
|
||||
// Dispose();
|
||||
// break;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Log.Error($"Unexpected exception: {ex.Message}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region ReceivePipeData
|
||||
//
|
||||
// private void ReceiveDataAsync(byte[] buffer, int receivedBytes, IPEndPoint remoteEndPoint)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var header = (KcpHeader)buffer[0];
|
||||
// var channelId = BitConverter.ToUInt32(buffer, 1);
|
||||
//
|
||||
// switch (header)
|
||||
// {
|
||||
// // 客户端请求建立KCP连接
|
||||
// case KcpHeader.RequestConnection:
|
||||
// {
|
||||
// if (_pendingConnection.TryGetValue(channelId, out var pendingConnection))
|
||||
// {
|
||||
// if (!remoteEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint))
|
||||
// {
|
||||
// // 重复通道ID,向客户端发送重复通道ID消息
|
||||
// SendRepeatChannelId(ref channelId, remoteEndPoint);
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (_connectionChannel.ContainsKey(channelId))
|
||||
// {
|
||||
// // 已存在的通道ID,向客户端发送重复通道ID消息
|
||||
// SendRepeatChannelId(ref channelId, remoteEndPoint);
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// AddPendingConnection(ref channelId, remoteEndPoint);
|
||||
// break;
|
||||
// }
|
||||
// // 客户端确认建立KCP连接
|
||||
// case KcpHeader.ConfirmConnection:
|
||||
// {
|
||||
// if (!ConfirmPendingConnection(ref channelId, remoteEndPoint))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// AddConnection(ref channelId, remoteEndPoint);
|
||||
// break;
|
||||
// }
|
||||
// // 接收KCP的数据
|
||||
// case KcpHeader.ReceiveData:
|
||||
// {
|
||||
// if (buffer.Length == 5)
|
||||
// {
|
||||
// Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5");
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (_connectionChannel.TryGetValue(channelId, out var channel))
|
||||
// {
|
||||
// channel.Input(buffer.AsMemory().Slice(5, receivedBytes - 5));
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// // 断开KCP连接
|
||||
// case KcpHeader.Disconnect:
|
||||
// {
|
||||
// // 断开不需要清楚PendingConnection让ClearPendingConnection自动清楚就可以了,并且不一定有Pending。
|
||||
// RemoveChannel(channelId);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// BufferPool.Return(buffer);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Update
|
||||
//
|
||||
// public void Update()
|
||||
// {
|
||||
// var timeNow = TimeNow;
|
||||
// _allowWraparound = timeNow < _updateMinTime;
|
||||
// CheckUpdateTimerOut(ref timeNow);
|
||||
// UpdateChannel(ref timeNow);
|
||||
// PendingTimerOut(ref timeNow);
|
||||
// _allowWraparound = true;
|
||||
// }
|
||||
//
|
||||
// private void CheckUpdateTimerOut(ref uint nowTime)
|
||||
// {
|
||||
// if (_updateTimer.Count == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (IsTimeGreaterThan(_updateMinTime, nowTime))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// _updateTimeOutTime.Clear();
|
||||
//
|
||||
// foreach (var kv in _updateTimer)
|
||||
// {
|
||||
// var timeId = kv.Key;
|
||||
//
|
||||
// if (IsTimeGreaterThan(timeId, nowTime))
|
||||
// {
|
||||
// _updateMinTime = timeId;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// _updateTimeOutTime.Add(timeId);
|
||||
// }
|
||||
//
|
||||
// foreach (var timeId in _updateTimeOutTime)
|
||||
// {
|
||||
// foreach (var channelId in _updateTimer[timeId])
|
||||
// {
|
||||
// _updateChannels.Add(channelId);
|
||||
// }
|
||||
//
|
||||
// _updateTimer.RemoveKey(timeId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void UpdateChannel(ref uint timeNow)
|
||||
// {
|
||||
// foreach (var channelId in _updateChannels)
|
||||
// {
|
||||
// if (!_connectionChannel.TryGetValue(channelId, out var channel))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (channel.IsDisposed)
|
||||
// {
|
||||
// _connectionChannel.Remove(channelId);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// channel.Kcp.Update(timeNow);
|
||||
// AddUpdateChannel(channelId, channel.Kcp.Check(timeNow));
|
||||
// }
|
||||
//
|
||||
// _updateChannels.Clear();
|
||||
// }
|
||||
//
|
||||
// private void PendingTimerOut(ref uint timeNow)
|
||||
// {
|
||||
// if (_pendingConnectionTimeOut.Count == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (IsTimeGreaterThan(_pendingMinTime, timeNow))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// _pendingTimeOutTime.Clear();
|
||||
//
|
||||
// foreach (var kv in _pendingConnectionTimeOut)
|
||||
// {
|
||||
// var timeId = kv.Key;
|
||||
//
|
||||
// if (IsTimeGreaterThan(timeId, timeNow))
|
||||
// {
|
||||
// _pendingMinTime = timeId;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// _pendingTimeOutTime.Add(timeId);
|
||||
// }
|
||||
//
|
||||
// foreach (var timeId in _pendingTimeOutTime)
|
||||
// {
|
||||
// foreach (var channelId in _pendingConnectionTimeOut[timeId])
|
||||
// {
|
||||
// _pendingConnection.Remove(channelId);
|
||||
// }
|
||||
//
|
||||
// _pendingConnectionTimeOut.RemoveKey(timeId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void AddUpdateChannel(uint channelId, uint tillTime)
|
||||
// {
|
||||
// if (tillTime == 0)
|
||||
// {
|
||||
// _updateChannels.Add(channelId);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (IsTimeGreaterThan(_updateMinTime, tillTime))
|
||||
// {
|
||||
// _updateMinTime = tillTime;
|
||||
// }
|
||||
//
|
||||
// _updateTimer.Add(tillTime, channelId);
|
||||
// }
|
||||
//
|
||||
// private const uint HalfMaxUint = uint.MaxValue / 2;
|
||||
//
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private bool IsTimeGreaterThan(uint timeId, uint nowTime)
|
||||
// {
|
||||
// if (!_allowWraparound)
|
||||
// {
|
||||
// return timeId > nowTime;
|
||||
// }
|
||||
//
|
||||
// var diff = timeId - nowTime;
|
||||
// // 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。
|
||||
// // 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。
|
||||
// return diff < HalfMaxUint || diff == HalfMaxUint;
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Pending
|
||||
//
|
||||
// private void AddPendingConnection(ref uint channelId, IPEndPoint ipEndPoint)
|
||||
// {
|
||||
// var now = TimeNow;
|
||||
// var pendingConnection = new PendingConnection(channelId, ipEndPoint, now);
|
||||
//
|
||||
// if (IsTimeGreaterThan(_pendingMinTime, pendingConnection.TimeOutId) || _pendingMinTime == 0)
|
||||
// {
|
||||
// _pendingMinTime = pendingConnection.TimeOutId;
|
||||
// }
|
||||
//
|
||||
// _pendingConnection.Add(channelId, pendingConnection);
|
||||
// _pendingConnectionTimeOut.Add(pendingConnection.TimeOutId, channelId);
|
||||
// SendWaitConfirmConnection(ref channelId, ipEndPoint);
|
||||
// }
|
||||
//
|
||||
// private bool ConfirmPendingConnection(ref uint channelId, EndPoint ipEndPoint)
|
||||
// {
|
||||
// if (!_pendingConnection.TryGetValue(channelId, out var pendingConnection))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint))
|
||||
// {
|
||||
// Log.Error($"KCPSocket syn address diff: {channelId} {pendingConnection.RemoteEndPoint} {ipEndPoint}");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// _pendingConnection.Remove(channelId);
|
||||
// _pendingConnectionTimeOut.RemoveValue(pendingConnection.TimeOutId, pendingConnection.ChannelId);
|
||||
// #if FANTASY_DEVELOP
|
||||
// Log.Debug($"KCPSocket _pendingConnection:{_pendingConnection.Count} _pendingConnectionTimer:{_pendingConnectionTimeOut.Count}");
|
||||
// #endif
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Connection
|
||||
//
|
||||
// private void AddConnection(ref uint channelId, IPEndPoint ipEndPoint)
|
||||
// {
|
||||
// var eventArgs = new KCPServerNetworkChannel(this, channelId, ipEndPoint);
|
||||
// _connectionChannel.Add(channelId, eventArgs);
|
||||
// #if FANTASY_DEVELOP
|
||||
// Log.Debug($"AddConnection _connectionChannel:{_connectionChannel.Count()}");
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// public override void RemoveChannel(uint channelId)
|
||||
// {
|
||||
// if (!_connectionChannel.Remove(channelId, out var channel))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (!channel.IsDisposed)
|
||||
// {
|
||||
// SendDisconnect(ref channelId, channel.RemoteEndPoint);
|
||||
// channel.Dispose();
|
||||
// }
|
||||
// #if FANTASY_DEVELOP
|
||||
// Log.Debug($"RemoveChannel _connectionChannel:{_connectionChannel.Count()}");
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Send
|
||||
//
|
||||
// private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect;
|
||||
// private const byte KcpHeaderRepeatChannelId = (byte)KcpHeader.RepeatChannelId;
|
||||
// private const byte KcpHeaderWaitConfirmConnection = (byte)KcpHeader.WaitConfirmConnection;
|
||||
//
|
||||
// private unsafe void SendDisconnect(ref uint channelId, EndPoint clientEndPoint)
|
||||
// {
|
||||
// fixed (byte* p = _sendBuff)
|
||||
// {
|
||||
// p[0] = KcpHeaderDisconnect;
|
||||
// *(uint*)(p + 1) = channelId;
|
||||
// }
|
||||
//
|
||||
// SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
// }
|
||||
//
|
||||
// private unsafe void SendRepeatChannelId(ref uint channelId, EndPoint clientEndPoint)
|
||||
// {
|
||||
// fixed (byte* p = _sendBuff)
|
||||
// {
|
||||
// p[0] = KcpHeaderRepeatChannelId;
|
||||
// *(uint*)(p + 1) = channelId;
|
||||
// }
|
||||
//
|
||||
// SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
// }
|
||||
//
|
||||
// private unsafe void SendWaitConfirmConnection(ref uint channelId, EndPoint clientEndPoint)
|
||||
// {
|
||||
// fixed (byte* p = _sendBuff)
|
||||
// {
|
||||
// p[0] = KcpHeaderWaitConfirmConnection;
|
||||
// *(uint*)(p + 1) = channelId;
|
||||
// }
|
||||
//
|
||||
// SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
// }
|
||||
//
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void SendAsync(byte[] buffer, int offset, int count, EndPoint endPoint)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// _socket.SendTo(new ArraySegment<byte>(buffer, offset, count), SocketFlags.None, endPoint);
|
||||
// }
|
||||
// catch (ArgumentException ex)
|
||||
// {
|
||||
// Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误
|
||||
// }
|
||||
// catch (SocketException)
|
||||
// {
|
||||
// //Log.Error($"SocketException: {ex.Message}"); // 处理网络错误
|
||||
// }
|
||||
// catch (ObjectDisposedException)
|
||||
// {
|
||||
// // 处理套接字已关闭的情况
|
||||
// }
|
||||
// catch (InvalidOperationException ex)
|
||||
// {
|
||||
// Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Log.Error($"Exception: {ex.Message}"); // 捕获其他异常
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
@@ -0,0 +1,619 @@
|
||||
#if FANTASY_NET
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.DataStructure.Collection;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
|
||||
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
|
||||
namespace Fantasy.Network.KCP
|
||||
{
|
||||
public sealed class KCPServerNetworkUpdateSystem : UpdateSystem<KCPServerNetwork>
|
||||
{
|
||||
protected override void Update(KCPServerNetwork self)
|
||||
{
|
||||
self.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public struct PendingConnection
|
||||
{
|
||||
public readonly uint ChannelId;
|
||||
public readonly uint TimeOutId;
|
||||
public readonly IPEndPoint RemoteEndPoint;
|
||||
|
||||
public PendingConnection(uint channelId, IPEndPoint remoteEndPoint, uint time)
|
||||
{
|
||||
ChannelId = channelId;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
TimeOutId = time + 10 * 1000; // 设置10秒超时,如果10秒内没有确认连接则删除。
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class KCPServerNetwork : ANetwork
|
||||
{
|
||||
private Socket _socket;
|
||||
private long _startTime;
|
||||
private uint _updateMinTime;
|
||||
private uint _pendingMinTime;
|
||||
private bool _allowWraparound = true;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly byte[] _sendBuff = new byte[5];
|
||||
private readonly List<uint> _pendingTimeOutTime = new List<uint>();
|
||||
private readonly HashSet<uint> _updateChannels = new HashSet<uint>();
|
||||
private readonly List<uint> _updateTimeOutTime = new List<uint>();
|
||||
private readonly Queue<IPEndPoint> _endPoint = new Queue<IPEndPoint>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly SortedOneToManyList<uint, uint> _updateTimer = new SortedOneToManyList<uint, uint>();
|
||||
|
||||
private readonly Dictionary<uint, PendingConnection> _pendingConnection = new Dictionary<uint, PendingConnection>();
|
||||
private readonly SortedOneToManyList<uint, uint> _pendingConnectionTimeOut = new SortedOneToManyList<uint, uint>();
|
||||
private readonly Dictionary<uint, KCPServerNetworkChannel> _connectionChannel = new Dictionary<uint, KCPServerNetworkChannel>();
|
||||
|
||||
public KCPSettings Settings { get; private set; }
|
||||
|
||||
private uint TimeNow => (uint)(TimeHelper.Now - _startTime);
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget, IPEndPoint address)
|
||||
{
|
||||
_startTime = TimeHelper.Now;
|
||||
Settings = KCPSettings.Create(networkTarget);
|
||||
base.Initialize(NetworkType.Server, NetworkProtocolType.KCP, networkTarget);
|
||||
_socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||
_socket.Blocking = false;
|
||||
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
_socket.Blocking = false;
|
||||
_socket.Bind(address);
|
||||
_socket.SetSocketBufferToOsLimit();
|
||||
_socket.SetSioUdpConnReset();
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} KCPServer Listen {address}");
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (_, channel) in _connectionChannel.ToArray())
|
||||
{
|
||||
channel.Dispose();
|
||||
}
|
||||
|
||||
_connectionChannel.Clear();
|
||||
_pendingConnection.Clear();
|
||||
|
||||
if (_socket != null)
|
||||
{
|
||||
_socket.Dispose();
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
var socketReceiveFromResult = await _socket.ReceiveFromAsync(memory, SocketFlags.None, remoteEndPoint, _cancellationTokenSource.Token);
|
||||
var receivedBytes = socketReceiveFromResult.ReceivedBytes;
|
||||
|
||||
if (receivedBytes == 5)
|
||||
{
|
||||
switch ((KcpHeader)memory.Span[0])
|
||||
{
|
||||
case KcpHeader.RequestConnection:
|
||||
case KcpHeader.ConfirmConnection:
|
||||
{
|
||||
_endPoint.Enqueue(socketReceiveFromResult.RemoteEndPoint.Clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pipe.Writer.Advance(receivedBytes);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Log.Error($"Socket exception: {ex.Message}");
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Unexpected exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var header, out var channelId, out var message))
|
||||
{
|
||||
ReceiveData(ref header, ref channelId, ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
|
||||
await pipeReader.CompleteAsync();
|
||||
}
|
||||
|
||||
private unsafe bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out KcpHeader header, out uint channelId, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length < 5)
|
||||
{
|
||||
channelId = 0;
|
||||
message = default;
|
||||
header = KcpHeader.None;
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
buffer = buffer.Slice(buffer.Length);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var readOnlyMemory = buffer.First;
|
||||
|
||||
if (MemoryMarshal.TryGetArray(readOnlyMemory, out var arraySegment))
|
||||
{
|
||||
fixed (byte* bytePointer = &arraySegment.Array[arraySegment.Offset])
|
||||
{
|
||||
header = (KcpHeader)bytePointer[0];
|
||||
channelId = Unsafe.ReadUnaligned<uint>(ref bytePointer[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果无法获取数组段,回退到安全代码来执行。这种情况几乎不会发生、为了保险还是写一下了。
|
||||
var firstSpan = readOnlyMemory.Span;
|
||||
header = (KcpHeader)firstSpan[0];
|
||||
channelId = MemoryMarshal.Read<uint>(firstSpan.Slice(1, 4));
|
||||
}
|
||||
|
||||
message = readOnlyMemory.Slice(5);
|
||||
buffer = buffer.Slice(readOnlyMemory.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref KcpHeader header, ref uint channelId, ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
switch (header)
|
||||
{
|
||||
// 客户端请求建立KCP连接
|
||||
case KcpHeader.RequestConnection:
|
||||
{
|
||||
_endPoint.TryDequeue(out var ipEndPoint);
|
||||
|
||||
if (_pendingConnection.TryGetValue(channelId, out var pendingConnection))
|
||||
{
|
||||
if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint))
|
||||
{
|
||||
// 重复通道ID,向客户端发送重复通道ID消息
|
||||
SendRepeatChannelId(ref channelId, ipEndPoint);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (_connectionChannel.ContainsKey(channelId))
|
||||
{
|
||||
// 已存在的通道ID,向客户端发送重复通道ID消息
|
||||
SendRepeatChannelId(ref channelId, ipEndPoint);
|
||||
break;
|
||||
}
|
||||
|
||||
AddPendingConnection(ref channelId, ipEndPoint);
|
||||
break;
|
||||
}
|
||||
// 客户端确认建立KCP连接
|
||||
case KcpHeader.ConfirmConnection:
|
||||
{
|
||||
_endPoint.TryDequeue(out var ipEndPoint);
|
||||
if (!ConfirmPendingConnection(ref channelId, ipEndPoint))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
AddConnection(ref channelId, ipEndPoint.Clone());
|
||||
break;
|
||||
}
|
||||
// 接收KCP的数据
|
||||
case KcpHeader.ReceiveData:
|
||||
{
|
||||
if (buffer.Length == 5)
|
||||
{
|
||||
Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5");
|
||||
break;
|
||||
}
|
||||
|
||||
if (_connectionChannel.TryGetValue(channelId, out var channel))
|
||||
{
|
||||
channel.Input(buffer);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// 断开KCP连接
|
||||
case KcpHeader.Disconnect:
|
||||
{
|
||||
// 断开不需要清楚PendingConnection让ClearPendingConnection自动清楚就可以了,并且不一定有Pending。
|
||||
RemoveChannel(channelId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var timeNow = TimeNow;
|
||||
_allowWraparound = timeNow < _updateMinTime;
|
||||
CheckUpdateTimerOut(ref timeNow);
|
||||
UpdateChannel(ref timeNow);
|
||||
PendingTimerOut(ref timeNow);
|
||||
_allowWraparound = true;
|
||||
}
|
||||
|
||||
private void CheckUpdateTimerOut(ref uint nowTime)
|
||||
{
|
||||
if (_updateTimer.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTimeGreaterThan(_updateMinTime, nowTime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updateTimeOutTime.Clear();
|
||||
|
||||
foreach (var kv in _updateTimer)
|
||||
{
|
||||
var timeId = kv.Key;
|
||||
|
||||
if (IsTimeGreaterThan(timeId, nowTime))
|
||||
{
|
||||
_updateMinTime = timeId;
|
||||
break;
|
||||
}
|
||||
|
||||
_updateTimeOutTime.Add(timeId);
|
||||
}
|
||||
|
||||
foreach (var timeId in _updateTimeOutTime)
|
||||
{
|
||||
foreach (var channelId in _updateTimer[timeId])
|
||||
{
|
||||
_updateChannels.Add(channelId);
|
||||
}
|
||||
|
||||
_updateTimer.RemoveKey(timeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChannel(ref uint timeNow)
|
||||
{
|
||||
foreach (var channelId in _updateChannels)
|
||||
{
|
||||
if (!_connectionChannel.TryGetValue(channelId, out var channel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (channel.IsDisposed)
|
||||
{
|
||||
_connectionChannel.Remove(channelId);
|
||||
continue;
|
||||
}
|
||||
|
||||
channel.Kcp.Update(timeNow);
|
||||
AddUpdateChannel(channelId, channel.Kcp.Check(timeNow));
|
||||
}
|
||||
|
||||
_updateChannels.Clear();
|
||||
}
|
||||
|
||||
private void PendingTimerOut(ref uint timeNow)
|
||||
{
|
||||
if (_pendingConnectionTimeOut.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTimeGreaterThan(_pendingMinTime, timeNow))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingTimeOutTime.Clear();
|
||||
|
||||
foreach (var kv in _pendingConnectionTimeOut)
|
||||
{
|
||||
var timeId = kv.Key;
|
||||
|
||||
if (IsTimeGreaterThan(timeId, timeNow))
|
||||
{
|
||||
_pendingMinTime = timeId;
|
||||
break;
|
||||
}
|
||||
|
||||
_pendingTimeOutTime.Add(timeId);
|
||||
}
|
||||
|
||||
foreach (var timeId in _pendingTimeOutTime)
|
||||
{
|
||||
foreach (var channelId in _pendingConnectionTimeOut[timeId])
|
||||
{
|
||||
_pendingConnection.Remove(channelId);
|
||||
}
|
||||
|
||||
_pendingConnectionTimeOut.RemoveKey(timeId);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUpdateChannel(uint channelId, uint tillTime)
|
||||
{
|
||||
if (tillTime == 0)
|
||||
{
|
||||
_updateChannels.Add(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTimeGreaterThan(_updateMinTime, tillTime))
|
||||
{
|
||||
_updateMinTime = tillTime;
|
||||
}
|
||||
|
||||
_updateTimer.Add(tillTime, channelId);
|
||||
}
|
||||
|
||||
private const uint HalfMaxUint = uint.MaxValue / 2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsTimeGreaterThan(uint timeId, uint nowTime)
|
||||
{
|
||||
if (!_allowWraparound)
|
||||
{
|
||||
return timeId > nowTime;
|
||||
}
|
||||
|
||||
var diff = timeId - nowTime;
|
||||
// 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。
|
||||
// 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。
|
||||
return diff < HalfMaxUint || diff == HalfMaxUint;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pending
|
||||
|
||||
private void AddPendingConnection(ref uint channelId, IPEndPoint ipEndPoint)
|
||||
{
|
||||
var now = TimeNow;
|
||||
var pendingConnection = new PendingConnection(channelId, ipEndPoint, now);
|
||||
|
||||
if (IsTimeGreaterThan(_pendingMinTime, pendingConnection.TimeOutId) || _pendingMinTime == 0)
|
||||
{
|
||||
_pendingMinTime = pendingConnection.TimeOutId;
|
||||
}
|
||||
|
||||
_pendingConnection.Add(channelId, pendingConnection);
|
||||
_pendingConnectionTimeOut.Add(pendingConnection.TimeOutId, channelId);
|
||||
SendWaitConfirmConnection(ref channelId, ipEndPoint);
|
||||
}
|
||||
|
||||
private bool ConfirmPendingConnection(ref uint channelId, EndPoint ipEndPoint)
|
||||
{
|
||||
if (!_pendingConnection.TryGetValue(channelId, out var pendingConnection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint))
|
||||
{
|
||||
Log.Error($"KCPSocket syn address diff: {channelId} {pendingConnection.RemoteEndPoint} {ipEndPoint}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingConnection.Remove(channelId);
|
||||
_pendingConnectionTimeOut.RemoveValue(pendingConnection.TimeOutId, pendingConnection.ChannelId);
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"KCPSocket _pendingConnection:{_pendingConnection.Count} _pendingConnectionTimer:{_pendingConnectionTimeOut.Count}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Connection
|
||||
|
||||
private void AddConnection(ref uint channelId, IPEndPoint ipEndPoint)
|
||||
{
|
||||
var eventArgs = new KCPServerNetworkChannel(this, channelId, ipEndPoint);
|
||||
_connectionChannel.Add(channelId, eventArgs);
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"AddConnection _connectionChannel:{_connectionChannel.Count()}");
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
if (!_connectionChannel.Remove(channelId, out var channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel.IsDisposed)
|
||||
{
|
||||
SendDisconnect(ref channelId, channel.RemoteEndPoint);
|
||||
channel.Dispose();
|
||||
}
|
||||
#if FANTASY_DEVELOP
|
||||
Log.Debug($"RemoveChannel _connectionChannel:{_connectionChannel.Count()}");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect;
|
||||
private const byte KcpHeaderRepeatChannelId = (byte)KcpHeader.RepeatChannelId;
|
||||
private const byte KcpHeaderWaitConfirmConnection = (byte)KcpHeader.WaitConfirmConnection;
|
||||
|
||||
private unsafe void SendDisconnect(ref uint channelId, EndPoint clientEndPoint)
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderDisconnect;
|
||||
*(uint*)(p + 1) = channelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
}
|
||||
|
||||
private unsafe void SendRepeatChannelId(ref uint channelId, EndPoint clientEndPoint)
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderRepeatChannelId;
|
||||
*(uint*)(p + 1) = channelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
}
|
||||
|
||||
private unsafe void SendWaitConfirmConnection(ref uint channelId, EndPoint clientEndPoint)
|
||||
{
|
||||
fixed (byte* p = _sendBuff)
|
||||
{
|
||||
p[0] = KcpHeaderWaitConfirmConnection;
|
||||
*(uint*)(p + 1) = channelId;
|
||||
}
|
||||
|
||||
SendAsync(_sendBuff, 0, 5, clientEndPoint);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SendAsync(byte[] buffer, int offset, int count, EndPoint endPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket.SendTo(new ArraySegment<byte>(buffer, offset, count), SocketFlags.None, endPoint);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
//Log.Error($"SocketException: {ex.Message}"); // 处理网络错误
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 处理套接字已关闭的情况
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception: {ex.Message}"); // 捕获其他异常
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,165 @@
|
||||
#if FANTASY_NET
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
using KCP;
|
||||
// ReSharper disable ParameterHidesMember
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.KCP
|
||||
{
|
||||
/// <summary>
|
||||
/// KCP 服务器网络通道,用于处理服务器与客户端之间的数据通信。
|
||||
/// </summary>
|
||||
public class KCPServerNetworkChannel : ANetworkServerChannel
|
||||
{
|
||||
private bool _isInnerDispose;
|
||||
private readonly int _maxSndWnd;
|
||||
private KCPServerNetwork _kcpServerNetwork;
|
||||
private readonly BufferPacketParser _packetParser;
|
||||
private readonly byte[] _receiveBuffer = new byte[Packet.PacketBodyMaxLength + 20];
|
||||
public Kcp Kcp { get; private set; }
|
||||
public uint ChannelId { get; private set; }
|
||||
|
||||
public KCPServerNetworkChannel(KCPServerNetwork network, uint channelId, IPEndPoint ipEndPoint) : base(network, channelId, ipEndPoint)
|
||||
{
|
||||
_kcpServerNetwork = network;
|
||||
ChannelId = channelId;
|
||||
_maxSndWnd = network.Settings.MaxSendWindowSize;
|
||||
Kcp = KCPFactory.Create(network.Settings, ChannelId, KcpSpanCallback);
|
||||
_packetParser = PacketParserFactory.CreateServerBufferPacket(network);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isInnerDispose = true;
|
||||
_kcpServerNetwork.RemoveChannel(Id);
|
||||
IsDisposed = true;
|
||||
Kcp.Dispose();
|
||||
Kcp = null;
|
||||
ChannelId = 0;
|
||||
_kcpServerNetwork = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Input(ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
Kcp.Input(buffer.Span);
|
||||
_kcpServerNetwork.AddUpdateChannel(ChannelId, 0);
|
||||
|
||||
while (!IsDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var peekSize = Kcp.PeekSize();
|
||||
|
||||
if (peekSize < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var receiveCount = Kcp.Receive(_receiveBuffer.AsSpan(0, peekSize));
|
||||
|
||||
if (receiveCount != peekSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_packetParser.UnPack(_receiveBuffer, ref receiveCount, out var packInfo))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Debug($"RemoteAddress:{RemoteEndPoint} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Kcp.WaitSendCount > _maxSndWnd)
|
||||
{
|
||||
// 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接
|
||||
Log.Warning($"ERR_KcpWaitSendSizeTooLarge {Kcp.WaitSendCount} > {_maxSndWnd}");
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message);
|
||||
Kcp.Send(buffer.GetBuffer().AsSpan(0, (int)buffer.Position));
|
||||
|
||||
if (buffer.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
_kcpServerNetwork.MemoryStreamBufferPool.ReturnMemoryStream(buffer);
|
||||
}
|
||||
|
||||
_kcpServerNetwork.AddUpdateChannel(ChannelId, 0);
|
||||
}
|
||||
|
||||
private const byte KcpHeaderReceiveData = (byte)KcpHeader.ReceiveData;
|
||||
|
||||
private unsafe void KcpSpanCallback(byte[] buffer, int count)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
throw new Exception("KcpOutput count 0");
|
||||
}
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
p[0] = KcpHeaderReceiveData;
|
||||
*(uint*)(p + 1) = ChannelId;
|
||||
}
|
||||
|
||||
_kcpServerNetwork.SendAsync(buffer, 0, count + 5, RemoteEndPoint);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
#if !FANTASY_WEBGL
|
||||
using Fantasy.Network.TCP;
|
||||
using Fantasy.Network.KCP;
|
||||
#endif
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Network.HTTP;
|
||||
#endif
|
||||
using Fantasy.Network.WebSocket;
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
internal static class NetworkProtocolFactory
|
||||
{
|
||||
#if FANTASY_NET
|
||||
public static ANetwork CreateServer(Scene scene, NetworkProtocolType protocolType, NetworkTarget networkTarget, string bindIp, int port)
|
||||
{
|
||||
switch (protocolType)
|
||||
{
|
||||
case NetworkProtocolType.TCP:
|
||||
{
|
||||
var network = Entity.Create<TCPServerNetwork>(scene, false, false);
|
||||
var address = NetworkHelper.ToIPEndPoint(bindIp, port);
|
||||
network.Initialize(networkTarget, address);
|
||||
return network;
|
||||
}
|
||||
case NetworkProtocolType.KCP:
|
||||
{
|
||||
var network = Entity.Create<KCPServerNetwork>(scene, false, true);
|
||||
var address = NetworkHelper.ToIPEndPoint(bindIp, port);
|
||||
network.Initialize(networkTarget, address);
|
||||
return network;
|
||||
}
|
||||
case NetworkProtocolType.WebSocket:
|
||||
{
|
||||
var network = Entity.Create<WebSocketServerNetwork>(scene, false, true);
|
||||
network.Initialize(networkTarget, bindIp, port);
|
||||
return network;
|
||||
}
|
||||
case NetworkProtocolType.HTTP:
|
||||
{
|
||||
var network = Entity.Create<HTTPServerNetwork>(scene, false, true);
|
||||
network.Initialize(networkTarget, bindIp, port);
|
||||
return network;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException($"Unsupported NetworkProtocolType:{protocolType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
public static AClientNetwork CreateClient(Scene scene, NetworkProtocolType protocolType, NetworkTarget networkTarget)
|
||||
{
|
||||
#if !FANTASY_WEBGL
|
||||
switch (protocolType)
|
||||
{
|
||||
case NetworkProtocolType.TCP:
|
||||
{
|
||||
var network = Entity.Create<TCPClientNetwork>(scene, false, false);
|
||||
network.Initialize(networkTarget);
|
||||
return network;
|
||||
}
|
||||
case NetworkProtocolType.KCP:
|
||||
{
|
||||
var network = Entity.Create<KCPClientNetwork>(scene, false, true);
|
||||
network.Initialize(networkTarget);
|
||||
return network;
|
||||
}
|
||||
case NetworkProtocolType.WebSocket:
|
||||
{
|
||||
var network = Entity.Create<WebSocketClientNetwork>(scene, false, true);
|
||||
network.Initialize(networkTarget);
|
||||
return network;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException($"Unsupported NetworkProtocolType:{protocolType}");
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Webgl平台只能用这个协议。
|
||||
var network = Entity.Create<WebSocketClientNetwork>(scene, false, true);
|
||||
network.Initialize(networkTarget);
|
||||
return network;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络服务器类型
|
||||
/// </summary>
|
||||
public enum NetworkType
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 客户端网络
|
||||
/// </summary>
|
||||
Client = 1,
|
||||
#if FANTASY_NET
|
||||
/// <summary>
|
||||
/// 服务器网络
|
||||
/// </summary>
|
||||
Server = 2
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 网络服务的目标
|
||||
/// </summary>
|
||||
public enum NetworkTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 对外
|
||||
/// </summary>
|
||||
Outer = 1,
|
||||
#if FANTASY_NET
|
||||
/// <summary>
|
||||
/// 对内
|
||||
/// </summary>
|
||||
Inner = 2
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 支持的网络协议
|
||||
/// </summary>
|
||||
public enum NetworkProtocolType
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// KCP
|
||||
/// </summary>
|
||||
KCP = 1,
|
||||
/// <summary>
|
||||
/// TCP
|
||||
/// </summary>
|
||||
TCP = 2,
|
||||
/// <summary>
|
||||
/// WebSocket
|
||||
/// </summary>
|
||||
WebSocket = 3,
|
||||
/// <summary>
|
||||
/// HTTP
|
||||
/// </summary>
|
||||
HTTP = 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#if !FANTASY_WEBGL
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Fantasy.Entitas;
|
||||
// ReSharper disable ForCanBeConvertedToForeach
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
internal interface INetworkThreadUpdate
|
||||
{
|
||||
void Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络线程组件
|
||||
/// </summary>
|
||||
internal sealed class NetworkThreadComponent : Entity
|
||||
{
|
||||
private Thread _netWorkThread;
|
||||
internal ThreadSynchronizationContext SynchronizationContext { get; private set; }
|
||||
private readonly List<INetworkThreadUpdate> _updates = new List<INetworkThreadUpdate>();
|
||||
|
||||
internal NetworkThreadComponent Initialize()
|
||||
{
|
||||
SynchronizationContext = new ThreadSynchronizationContext();
|
||||
_netWorkThread = new Thread(Update)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
_netWorkThread.Start();
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SynchronizationContext.Post(() =>
|
||||
{
|
||||
_updates.Clear();
|
||||
_netWorkThread.Join();
|
||||
_netWorkThread = null;
|
||||
SynchronizationContext = null;
|
||||
});
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 将同步上下文设置为网络线程的上下文,以确保操作在正确的线程上下文中执行。
|
||||
System.Threading.SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
|
||||
// 循环执行
|
||||
while (!IsDisposed)
|
||||
{
|
||||
for (var i = 0; i < _updates.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
_updates[i].Update();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
SynchronizationContext.Update();
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddNetworkThreadUpdate(INetworkThreadUpdate update)
|
||||
{
|
||||
SynchronizationContext.Post(() =>
|
||||
{
|
||||
if (_updates.Contains(update))
|
||||
{
|
||||
Log.Warning($"{update.GetType().FullName} Network thread update is already running");
|
||||
return;
|
||||
}
|
||||
_updates.Add(update);
|
||||
});
|
||||
}
|
||||
|
||||
internal void RemoveNetworkThreadUpdate(INetworkThreadUpdate update)
|
||||
{
|
||||
SynchronizationContext.Post(() =>
|
||||
{
|
||||
_updates.Remove(update);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,413 @@
|
||||
#if !FANTASY_WEBGL
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
|
||||
namespace Fantasy.Network.TCP
|
||||
{
|
||||
public sealed class TCPClientNetwork : AClientNetwork
|
||||
{
|
||||
private bool _isSending;
|
||||
private bool _isInnerDispose;
|
||||
private long _connectTimeoutId;
|
||||
private Socket _socket;
|
||||
private IPEndPoint _remoteEndPoint;
|
||||
private SocketAsyncEventArgs _sendArgs;
|
||||
private ReadOnlyMemoryPacketParser _packetParser;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly Queue<MemoryStreamBuffer> _sendBuffers = new Queue<MemoryStreamBuffer>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private Action _onConnectFail;
|
||||
private Action _onConnectComplete;
|
||||
private Action _onConnectDisconnect;
|
||||
|
||||
public uint ChannelId { get; private set; }
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget)
|
||||
{
|
||||
base.Initialize(NetworkType.Client, NetworkProtocolType.TCP, networkTarget);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isSending = false;
|
||||
_isInnerDispose = true;
|
||||
ClearConnectTimeout();
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
|
||||
_onConnectDisconnect?.Invoke();
|
||||
|
||||
if (_socket.Connected)
|
||||
{
|
||||
_socket.Close();
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
_sendBuffers.Clear();
|
||||
_packetParser?.Dispose();
|
||||
ChannelId = 0;
|
||||
_sendArgs = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程服务器。
|
||||
/// </summary>
|
||||
/// <param name="remoteAddress">远程服务器的终端点。</param>
|
||||
/// <param name="onConnectComplete">连接成功时的回调。</param>
|
||||
/// <param name="onConnectFail">连接失败时的回调。</param>
|
||||
/// <param name="onConnectDisconnect">连接断开时的回调。</param>
|
||||
/// <param name="isHttps"></param>
|
||||
/// <param name="connectTimeout">连接超时时间,单位:毫秒。</param>
|
||||
/// <returns>连接的会话。</returns>
|
||||
public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000)
|
||||
{
|
||||
// 如果已经初始化过一次,抛出异常,要求重新实例化
|
||||
|
||||
if (IsInit)
|
||||
{
|
||||
throw new NotSupportedException("TCPClientNetwork Has already been initialized. If you want to call Connect again, please re instantiate it.");
|
||||
}
|
||||
|
||||
IsInit = true;
|
||||
_isSending = false;
|
||||
_onConnectFail = onConnectFail;
|
||||
_onConnectComplete = onConnectComplete;
|
||||
_onConnectDisconnect = onConnectDisconnect;
|
||||
// 设置连接超时定时器
|
||||
_connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () =>
|
||||
{
|
||||
_onConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
_packetParser = PacketParserFactory.CreateClientReadOnlyMemoryPacket(this);
|
||||
_remoteEndPoint = NetworkHelper.GetIPEndPoint(remoteAddress);
|
||||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.NoDelay = true;
|
||||
_socket.SetSocketBufferToOsLimit();
|
||||
_sendArgs = new SocketAsyncEventArgs();
|
||||
_sendArgs.Completed += OnSendCompleted;
|
||||
var outArgs = new SocketAsyncEventArgs
|
||||
{
|
||||
RemoteEndPoint = _remoteEndPoint
|
||||
};
|
||||
outArgs.Completed += OnConnectSocketCompleted;
|
||||
|
||||
if (!_socket.ConnectAsync(outArgs))
|
||||
{
|
||||
OnReceiveSocketComplete();
|
||||
}
|
||||
|
||||
Session = Session.Create(this, _remoteEndPoint);
|
||||
return Session;
|
||||
}
|
||||
|
||||
private void OnConnectSocketCompleted(object sender, SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (asyncEventArgs.LastOperation == SocketAsyncOperation.Connect)
|
||||
{
|
||||
if (asyncEventArgs.SocketError == SocketError.Success)
|
||||
{
|
||||
Scene.ThreadSynchronizationContext.Post(OnReceiveSocketComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
_onConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceiveSocketComplete()
|
||||
{
|
||||
ClearConnectTimeout();
|
||||
_onConnectComplete?.Invoke();
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
}
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
#if UNITY_2021
|
||||
// Unity2021.3.14f有个恶心的问题,使用ReceiveAsync会导致memory不能正确写入
|
||||
// 所有只能使用ReceiveFromAsync来接收消息,但ReceiveFromAsync只有一个接受ArraySegment的接口。
|
||||
MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> arraySegment);
|
||||
var result = await _socket.ReceiveFromAsync(arraySegment, SocketFlags.None, _remoteEndPoint);
|
||||
_pipe.Writer.Advance(result.ReceivedBytes);
|
||||
#else
|
||||
var count = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token);
|
||||
_pipe.Writer.Advance(count);
|
||||
#endif
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Unexpected exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var message))
|
||||
{
|
||||
ReceiveData(ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
|
||||
await pipeReader.CompleteAsync();
|
||||
}
|
||||
|
||||
private bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = buffer.First;
|
||||
|
||||
if (message.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = buffer.Slice(message.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_packetParser.UnPack(ref buffer, out var packInfo))
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Warning(e.Message);
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
_sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message));
|
||||
|
||||
if (!_isSending)
|
||||
{
|
||||
Send();
|
||||
}
|
||||
}
|
||||
|
||||
private void Send()
|
||||
{
|
||||
if (_isSending || IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSending = true;
|
||||
|
||||
while (_sendBuffers.Count > 0)
|
||||
{
|
||||
var memoryStreamBuffer = _sendBuffers.Dequeue();
|
||||
_sendArgs.UserToken = memoryStreamBuffer;
|
||||
_sendArgs.SetBuffer(new ArraySegment<byte>(memoryStreamBuffer.GetBuffer(), 0, (int)memoryStreamBuffer.Position));
|
||||
|
||||
try
|
||||
{
|
||||
if (_socket.SendAsync(_sendArgs))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ReturnMemoryStream(memoryStreamBuffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_isSending = false;
|
||||
}
|
||||
|
||||
private void ReturnMemoryStream(MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSendCompleted(object sender, SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0)
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var memoryStreamBuffer = (MemoryStreamBuffer)asyncEventArgs.UserToken;
|
||||
Scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
ReturnMemoryStream(memoryStreamBuffer);
|
||||
|
||||
if (_sendBuffers.Count > 0)
|
||||
{
|
||||
Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isSending = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ClearConnectTimeout()
|
||||
{
|
||||
if (_connectTimeoutId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
#if FANTASY_NET
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
// ReSharper disable GCSuppressFinalizeForTypeWithoutDestructor
|
||||
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
namespace Fantasy.Network.TCP
|
||||
{
|
||||
public sealed class TCPServerNetwork : ANetwork
|
||||
{
|
||||
private Random _random;
|
||||
private Socket _socket;
|
||||
private SocketAsyncEventArgs _acceptAsync;
|
||||
private readonly Dictionary<uint, INetworkChannel> _connectionChannel = new Dictionary<uint, INetworkChannel>();
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget, IPEndPoint address)
|
||||
{
|
||||
base.Initialize(NetworkType.Server, NetworkProtocolType.TCP, networkTarget);
|
||||
_random = new Random();
|
||||
_acceptAsync = new SocketAsyncEventArgs();
|
||||
_socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
|
||||
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
_socket.Bind(address);
|
||||
_socket.Listen(int.MaxValue);
|
||||
_socket.SetSocketBufferToOsLimit();
|
||||
Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} TCPServer Listen {address}");
|
||||
_acceptAsync.Completed += OnCompleted;
|
||||
AcceptAsync();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var networkChannel in _connectionChannel.Values.ToArray())
|
||||
{
|
||||
networkChannel.Dispose();
|
||||
}
|
||||
|
||||
_connectionChannel.Clear();
|
||||
_random = null;
|
||||
_socket.Dispose();
|
||||
_socket = null;
|
||||
_acceptAsync.Dispose();
|
||||
_acceptAsync = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void AcceptAsync()
|
||||
{
|
||||
_acceptAsync.AcceptSocket = null;
|
||||
|
||||
if (_socket.AcceptAsync(_acceptAsync))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnAcceptComplete(_acceptAsync);
|
||||
}
|
||||
|
||||
private void OnAcceptComplete(SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
if (asyncEventArgs.AcceptSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (asyncEventArgs.SocketError != SocketError.Success)
|
||||
{
|
||||
Log.Error($"Socket Accept Error: {_acceptAsync.SocketError}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
uint channelId;
|
||||
do
|
||||
{
|
||||
channelId = 0xC0000000 | (uint)_random.Next();
|
||||
} while (_connectionChannel.ContainsKey(channelId));
|
||||
|
||||
_connectionChannel.Add(channelId, new TCPServerNetworkChannel(this, asyncEventArgs.AcceptSocket, channelId));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AcceptAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
if (IsDisposed || !_connectionChannel.Remove(channelId, out var channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
channel.Dispose();
|
||||
}
|
||||
|
||||
#region 网络线程(由Socket底层产生的线程)
|
||||
|
||||
private void OnCompleted(object sender, SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
switch (asyncEventArgs.LastOperation)
|
||||
{
|
||||
case SocketAsyncOperation.Accept:
|
||||
{
|
||||
Scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
OnAcceptComplete(asyncEventArgs);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception($"Socket Accept Error: {asyncEventArgs.LastOperation}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
#if FANTASY_NET
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Sockets;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||
|
||||
namespace Fantasy.Network.TCP
|
||||
{
|
||||
public sealed class TCPServerNetworkChannel : ANetworkServerChannel
|
||||
{
|
||||
private bool _isSending;
|
||||
private bool _isInnerDispose;
|
||||
private readonly Socket _socket;
|
||||
private readonly ANetwork _network;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly SocketAsyncEventArgs _sendArgs;
|
||||
private readonly ReadOnlyMemoryPacketParser _packetParser;
|
||||
private readonly Queue<MemoryStreamBuffer> _sendBuffers = new Queue<MemoryStreamBuffer>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
public TCPServerNetworkChannel(ANetwork network, Socket socket, uint id) : base(network, id, socket.RemoteEndPoint)
|
||||
{
|
||||
_socket = socket;
|
||||
_network = network;
|
||||
_socket.NoDelay = true;
|
||||
_sendArgs = new SocketAsyncEventArgs();
|
||||
_sendArgs.Completed += OnSendCompletedHandler;
|
||||
_packetParser = PacketParserFactory.CreateServerReadOnlyMemoryPacket(network);
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isInnerDispose = true;
|
||||
_network.RemoveChannel(Id);
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
|
||||
if (_socket != null)
|
||||
{
|
||||
_socket.Shutdown(SocketShutdown.Both);
|
||||
_socket.Close();
|
||||
}
|
||||
|
||||
_sendBuffers.Clear();
|
||||
_packetParser.Dispose();
|
||||
_isSending = false;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
var count = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
_pipe.Writer.Advance(count);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Unexpected exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var message))
|
||||
{
|
||||
ReceiveData(ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
|
||||
await pipeReader.CompleteAsync();
|
||||
}
|
||||
|
||||
private bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = buffer.First;
|
||||
|
||||
if (message.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = buffer.Slice(message.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_packetParser.UnPack(ref buffer, out var packInfo))
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Warning($"RemoteAddress:{RemoteEndPoint} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"RemoteAddress:{RemoteEndPoint} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
_sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message));
|
||||
|
||||
if (!_isSending)
|
||||
{
|
||||
Send();
|
||||
}
|
||||
}
|
||||
|
||||
private void Send()
|
||||
{
|
||||
if (_isSending || IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSending = true;
|
||||
|
||||
while (_sendBuffers.Count > 0)
|
||||
{
|
||||
var memoryStreamBuffer = _sendBuffers.Dequeue();
|
||||
_sendArgs.UserToken = memoryStreamBuffer;
|
||||
_sendArgs.SetBuffer(new ArraySegment<byte>(memoryStreamBuffer.GetBuffer(), 0, (int)memoryStreamBuffer.Position));
|
||||
|
||||
try
|
||||
{
|
||||
if (_socket.SendAsync(_sendArgs))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ReturnMemoryStream(memoryStreamBuffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_isSending = false;
|
||||
}
|
||||
|
||||
private void ReturnMemoryStream(MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
_network.MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSendCompletedHandler(object sender, SocketAsyncEventArgs asyncEventArgs)
|
||||
{
|
||||
if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0)
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var memoryStreamBuffer = (MemoryStreamBuffer)asyncEventArgs.UserToken;
|
||||
|
||||
Scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
ReturnMemoryStream(memoryStreamBuffer);
|
||||
|
||||
if (_sendBuffers.Count > 0)
|
||||
{
|
||||
Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isSending = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,345 @@
|
||||
#if FANTASY_NET || FANTASY_CONSOLE
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.WebSockets;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
namespace Fantasy.Network.WebSocket
|
||||
{
|
||||
public sealed class WebSocketClientNetwork : AClientNetwork
|
||||
{
|
||||
private bool _isSending;
|
||||
private bool _isInnerDispose;
|
||||
private long _connectTimeoutId;
|
||||
private ClientWebSocket _clientWebSocket;
|
||||
private ReadOnlyMemoryPacketParser _packetParser;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly Queue<MemoryStreamBuffer> _sendBuffers = new Queue<MemoryStreamBuffer>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private Action _onConnectFail;
|
||||
private Action _onConnectComplete;
|
||||
private Action _onConnectDisconnect;
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget)
|
||||
{
|
||||
base.Initialize(NetworkType.Client, NetworkProtocolType.WebSocket, networkTarget);
|
||||
_packetParser = PacketParserFactory.CreateClientReadOnlyMemoryPacket(this);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isInnerDispose = true;
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
|
||||
ClearConnectTimeout();
|
||||
WebSocketClientDisposeAsync().Coroutine();
|
||||
_onConnectDisconnect?.Invoke();
|
||||
_packetParser.Dispose();
|
||||
_packetParser = null;
|
||||
_isSending = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async FTask WebSocketClientDisposeAsync()
|
||||
{
|
||||
if (_clientWebSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
|
||||
_clientWebSocket.Dispose();
|
||||
_clientWebSocket = null;
|
||||
}
|
||||
|
||||
public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000)
|
||||
{
|
||||
if (IsInit)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"WebSocketClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it.");
|
||||
}
|
||||
|
||||
IsInit = true;
|
||||
_onConnectFail = onConnectFail;
|
||||
_onConnectComplete = onConnectComplete;
|
||||
_onConnectDisconnect = onConnectDisconnect;
|
||||
// 设置连接超时定时器
|
||||
_connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () =>
|
||||
{
|
||||
_onConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
|
||||
_clientWebSocket = new ClientWebSocket();
|
||||
var webSocketAddress = WebSocketHelper.GetWebSocketAddress(remoteAddress, isHttps);
|
||||
|
||||
try
|
||||
{
|
||||
_clientWebSocket.ConnectAsync(new Uri(webSocketAddress), _cancellationTokenSource.Token).Wait();
|
||||
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (WebSocketException wse)
|
||||
{
|
||||
Log.Error($"WebSocket error: {wse.Message}");
|
||||
Dispose();
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"An error occurred: {e.Message}");
|
||||
Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
ClearConnectTimeout();
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
_onConnectComplete?.Invoke();
|
||||
Session = Session.Create(this, null);
|
||||
return Session;
|
||||
}
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
// 这里接收的数据不一定是一个完整的包。如果大于8192就会分成多个包。
|
||||
var receiveResult = await _clientWebSocket.ReceiveAsync(memory, _cancellationTokenSource.Token);
|
||||
|
||||
if (receiveResult.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var count = receiveResult.Count;
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await PipeWriterFlushAsync(count);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
// 这个先暂时注释掉,因为有些时候会出现WebSocketException
|
||||
// 因为会出现这个挥手的错误,下个版本处理一下。
|
||||
// The remote party closed the WebSocket connection without completing the close handshake.
|
||||
// catch (WebSocketException wse)
|
||||
// {
|
||||
// Log.Error($"WebSocket error: {wse.Message}");
|
||||
// Dispose();
|
||||
// break;
|
||||
// }
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
private async FTask PipeWriterFlushAsync(int count)
|
||||
{
|
||||
_pipe.Writer.Advance(count);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var message))
|
||||
{
|
||||
ReceiveData(ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
|
||||
await pipeReader.CompleteAsync();
|
||||
}
|
||||
|
||||
private bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = buffer.First;
|
||||
|
||||
if (message.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = buffer.Slice(message.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_packetParser.UnPack(ref buffer, out var packInfo))
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Warning(e.Message);
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
_sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message));
|
||||
|
||||
if (!_isSending)
|
||||
{
|
||||
Send().Coroutine();
|
||||
}
|
||||
}
|
||||
|
||||
private async FTask Send()
|
||||
{
|
||||
if (_isSending || IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSending = true;
|
||||
|
||||
while (_isSending)
|
||||
{
|
||||
if (!_sendBuffers.TryDequeue(out var memoryStream))
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await _clientWebSocket.SendAsync(new ArraySegment<byte>(memoryStream.GetBuffer(), 0, (int)memoryStream.Position), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
|
||||
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ClearConnectTimeout()
|
||||
{
|
||||
if (_connectTimeoutId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,190 @@
|
||||
#if !FANTASY_NET && !FANTASY_CONSOLE
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
using UnityWebSocket;
|
||||
|
||||
namespace Fantasy.Network.WebSocket
|
||||
{
|
||||
// 因为webgl的限制、注定这个要是在游戏主线程里。所以这个库不会再其他线程执行的。
|
||||
// WebGL:在WebGL环境下运行
|
||||
// 另外不是运行在WebGL环境下,也没必要使用WebSocket协议了。完全可以使用TCP或KCP运行。同样也不会有那个队列产生的GC。
|
||||
public class WebSocketClientNetwork : AClientNetwork
|
||||
{
|
||||
private UnityWebSocket.WebSocket _webSocket;
|
||||
private bool _isInnerDispose;
|
||||
private bool _isConnected;
|
||||
private long _connectTimeoutId;
|
||||
private BufferPacketParser _packetParser;
|
||||
private readonly Queue<MemoryStreamBuffer> _messageCache = new Queue<MemoryStreamBuffer>();
|
||||
|
||||
private Action _onConnectFail;
|
||||
private Action _onConnectComplete;
|
||||
private Action _onConnectDisconnect;
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget)
|
||||
{
|
||||
base.Initialize(NetworkType.Client, NetworkProtocolType.WebSocket, networkTarget);
|
||||
_packetParser = PacketParserFactory.CreateClient<BufferPacketParser>(this);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isInnerDispose = true;
|
||||
ClearConnectTimeout();
|
||||
|
||||
if (_webSocket != null && _webSocket.ReadyState != WebSocketState.Closed)
|
||||
{
|
||||
_onConnectDisconnect?.Invoke();
|
||||
_webSocket.CloseAsync();
|
||||
}
|
||||
|
||||
_packetParser.Dispose();
|
||||
_messageCache.Clear();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000)
|
||||
{
|
||||
// 如果已经初始化过一次,抛出异常,要求重新实例化
|
||||
|
||||
if (IsInit)
|
||||
{
|
||||
throw new NotSupportedException($"WebSocketClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it.");
|
||||
}
|
||||
|
||||
IsInit = true;
|
||||
_onConnectFail = onConnectFail;
|
||||
_onConnectComplete = onConnectComplete;
|
||||
_onConnectDisconnect = onConnectDisconnect;
|
||||
_connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () =>
|
||||
{
|
||||
_onConnectFail?.Invoke();
|
||||
Dispose();
|
||||
});
|
||||
var webSocketAddress = WebSocketHelper.GetWebSocketAddress(remoteAddress, isHttps);
|
||||
_webSocket = new UnityWebSocket.WebSocket(webSocketAddress);
|
||||
_webSocket.OnOpen += OnNetworkConnectComplete;
|
||||
_webSocket.OnMessage += OnReceiveComplete;
|
||||
_webSocket.OnClose += (sender, args) =>
|
||||
{
|
||||
_onConnectDisconnect?.Invoke();
|
||||
Dispose();
|
||||
};
|
||||
_webSocket.ConnectAsync();
|
||||
Session = Session.Create(this, null);
|
||||
return Session;
|
||||
}
|
||||
|
||||
private void OnNetworkConnectComplete(object sender, OpenEventArgs e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isConnected = true;
|
||||
ClearConnectTimeout();
|
||||
_onConnectComplete?.Invoke();
|
||||
|
||||
while (_messageCache.TryDequeue(out var memoryStream))
|
||||
{
|
||||
Send(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
#region Receive
|
||||
|
||||
private void OnReceiveComplete(object sender, MessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// WebSocket 协议已经在协议层面处理了消息的边界问题,因此不需要额外的粘包处理逻辑。
|
||||
// 所以如果解包的时候出现任何错误只能是恶意攻击造成的。
|
||||
var rawDataLength = e.RawData.Length;
|
||||
_packetParser.UnPack(e.RawData, ref rawDataLength, out var packInfo);
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
catch (ScanException ex)
|
||||
{
|
||||
Log.Warning($"{ex}");
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message);
|
||||
|
||||
if (!_isConnected)
|
||||
{
|
||||
_messageCache.Enqueue(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
Send(buffer);
|
||||
}
|
||||
|
||||
private void Send(MemoryStreamBuffer memoryStream)
|
||||
{
|
||||
_webSocket.SendAsync(memoryStream.GetBuffer(), 0, (int)memoryStream.Position);
|
||||
#if !UNITY_EDITOR && UNITY_WEBGL
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ClearConnectTimeout()
|
||||
{
|
||||
if (_connectTimeoutId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#if FANTASY_NET
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
namespace Fantasy.Network.WebSocket;
|
||||
|
||||
public class WebSocketServerNetwork : ANetwork
|
||||
{
|
||||
private Random _random;
|
||||
private HttpListener _httpListener;
|
||||
private readonly Dictionary<uint, INetworkChannel> _connectionChannel = new Dictionary<uint, INetworkChannel>();
|
||||
|
||||
public void Initialize(NetworkTarget networkTarget, string bindIp, int port)
|
||||
{
|
||||
base.Initialize(NetworkType.Server, NetworkProtocolType.WebSocket, networkTarget);
|
||||
|
||||
try
|
||||
{
|
||||
_random = new Random();
|
||||
_httpListener = new HttpListener();
|
||||
StartAcceptAsync(bindIp, port).Coroutine();
|
||||
}
|
||||
catch (HttpListenerException e)
|
||||
{
|
||||
if (e.ErrorCode == 5)
|
||||
{
|
||||
throw new Exception($"CMD管理员中输入: netsh http add urlacl url=http://*:8080/ user=Everyone", e);
|
||||
}
|
||||
|
||||
Log.Error(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_httpListener != null)
|
||||
{
|
||||
_httpListener.Close();
|
||||
_httpListener = null;
|
||||
}
|
||||
|
||||
foreach (var channel in _connectionChannel.Values.ToArray())
|
||||
{
|
||||
channel.Dispose();
|
||||
}
|
||||
|
||||
_connectionChannel.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
private async FTask StartAcceptAsync(string bindIp, int port)
|
||||
{
|
||||
var listenUrl = "";
|
||||
var certificatePath = Path.Combine(AppContext.BaseDirectory, $"certificate{bindIp}{port}");
|
||||
listenUrl = Directory.Exists(certificatePath) ? $"https://{bindIp}:{port}/" : $"http://{bindIp}:{port}/";
|
||||
_httpListener.Prefixes.Add(listenUrl);
|
||||
_httpListener.Start();
|
||||
Log.Info($"SceneConfigId = {Scene.SceneConfigId} WebSocketServer Listen {listenUrl}");
|
||||
while (!IsDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpListenerContext = await _httpListener.GetContextAsync();
|
||||
var webSocketContext = await httpListenerContext.AcceptWebSocketAsync(null);
|
||||
var channelId = 0xC0000000 | (uint) _random.Next();
|
||||
|
||||
while (_connectionChannel.ContainsKey(channelId))
|
||||
{
|
||||
channelId = 0xC0000000 | (uint) _random.Next();
|
||||
}
|
||||
|
||||
_connectionChannel.Add(channelId, new WebSocketServerNetworkChannel(this, channelId, webSocketContext, httpListenerContext.Request.RemoteEndPoint));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveChannel(uint channelId)
|
||||
{
|
||||
if (IsDisposed || !_connectionChannel.Remove(channelId, out var channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
channel.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,253 @@
|
||||
#if FANTASY_NET
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.WebSockets;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.Serialize;
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Network.WebSocket;
|
||||
|
||||
public sealed class WebSocketServerNetworkChannel : ANetworkServerChannel
|
||||
{
|
||||
private bool _isSending;
|
||||
private bool _isInnerDispose;
|
||||
private readonly Pipe _pipe = new Pipe();
|
||||
private readonly System.Net.WebSockets.WebSocket _webSocket;
|
||||
private readonly WebSocketServerNetwork _network;
|
||||
private readonly ReadOnlyMemoryPacketParser _packetParser;
|
||||
private readonly Queue<MemoryStreamBuffer> _sendBuffers = new Queue<MemoryStreamBuffer>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
public WebSocketServerNetworkChannel(ANetwork network, uint id, HttpListenerWebSocketContext httpListenerWebSocketContext, IPEndPoint remoteEndPoint) : base(network, id, remoteEndPoint)
|
||||
{
|
||||
_network = (WebSocketServerNetwork)network;
|
||||
_webSocket = httpListenerWebSocketContext.WebSocket;
|
||||
_packetParser = PacketParserFactory.CreateServerReadOnlyMemoryPacket(network);
|
||||
ReadPipeDataAsync().Coroutine();
|
||||
ReceiveSocketAsync().Coroutine();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed || _isInnerDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isInnerDispose = true;
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 通常情况下,此处的异常可以忽略
|
||||
}
|
||||
}
|
||||
_sendBuffers.Clear();
|
||||
_network.RemoveChannel(Id);
|
||||
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.CloseReceived)
|
||||
{
|
||||
_webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure",
|
||||
_cancellationTokenSource.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
_webSocket.Dispose();
|
||||
_isSending = false;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
#region ReceiveSocket
|
||||
|
||||
private async FTask ReceiveSocketAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var memory = _pipe.Writer.GetMemory(8192);
|
||||
// 这里接收的数据不一定是一个完整的包。如果大于8192就会分成多个包。
|
||||
var receiveResult = await _webSocket.ReceiveAsync(memory, _cancellationTokenSource.Token);
|
||||
|
||||
if (receiveResult.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
var count = receiveResult.Count;
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await PipeWriterFlushAsync(count);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
// Log.Error($"WebSocket error: {wse.Message}");
|
||||
Dispose();
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
await _pipe.Writer.CompleteAsync();
|
||||
}
|
||||
|
||||
private async FTask PipeWriterFlushAsync(int count)
|
||||
{
|
||||
_pipe.Writer.Advance(count);
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReceivePipeData
|
||||
|
||||
private async FTask ReadPipeDataAsync()
|
||||
{
|
||||
var pipeReader = _pipe.Reader;
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
ReadResult result = default;
|
||||
|
||||
try
|
||||
{
|
||||
result = await pipeReader.ReadAsync(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。
|
||||
break;
|
||||
}
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
|
||||
while (TryReadMessage(ref buffer, out var message))
|
||||
{
|
||||
ReceiveData(ref message);
|
||||
consumed = buffer.Start;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
|
||||
await pipeReader.CompleteAsync();
|
||||
}
|
||||
|
||||
private bool TryReadMessage(ref ReadOnlySequence<byte> buffer, out ReadOnlyMemory<byte> message)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = buffer.First;
|
||||
|
||||
if (message.Length == 0)
|
||||
{
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = buffer.Slice(message.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ReceiveData(ref ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_packetParser.UnPack(ref buffer, out var packInfo))
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Session.Receive(packInfo);
|
||||
}
|
||||
}
|
||||
catch (ScanException e)
|
||||
{
|
||||
Log.Warning($"RemoteAddress:{RemoteEndPoint} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"RemoteAddress:{RemoteEndPoint} \n{e}");
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message)
|
||||
{
|
||||
_sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message));
|
||||
|
||||
if (!_isSending)
|
||||
{
|
||||
Send().Coroutine();
|
||||
}
|
||||
}
|
||||
|
||||
private async FTask Send()
|
||||
{
|
||||
if (_isSending || IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSending = true;
|
||||
|
||||
while (_isSending)
|
||||
{
|
||||
if (!_sendBuffers.TryDequeue(out var memoryStream))
|
||||
{
|
||||
_isSending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(memoryStream.GetBuffer(), 0, (int)memoryStream.Position), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
|
||||
|
||||
if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack)
|
||||
{
|
||||
_network.MemoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,254 @@
|
||||
#if FANTASY_NET
|
||||
using System.Runtime.CompilerServices;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Timer;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
/// <summary>
|
||||
/// 漫游组件,用来管理当然Scene下的所有漫游消息。
|
||||
/// 大多数是在Gate这样的转发服务器上创建的。
|
||||
/// </summary>
|
||||
public sealed class RoamingComponent : Entity
|
||||
{
|
||||
private TimerSchedulerNet _timerSchedulerNet;
|
||||
private readonly Dictionary<long, SessionRoamingComponent> _sessionRoamingComponents = new();
|
||||
private readonly Dictionary<long, long> _delayRemoveTaskId = new();
|
||||
|
||||
internal RoamingComponent Initialize()
|
||||
{
|
||||
_timerSchedulerNet = Scene.TimerComponent.Net;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁方法。
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
DisposeAsync().Coroutine();
|
||||
}
|
||||
|
||||
private async FTask DisposeAsync()
|
||||
{
|
||||
foreach (var (_,taskId) in _delayRemoveTaskId)
|
||||
{
|
||||
_timerSchedulerNet.Remove(taskId);
|
||||
}
|
||||
|
||||
_delayRemoveTaskId.Clear();
|
||||
|
||||
foreach (var (_, sessionRoamingComponent) in _sessionRoamingComponents)
|
||||
{
|
||||
await sessionRoamingComponent.UnLink();
|
||||
}
|
||||
|
||||
_sessionRoamingComponents.Clear();
|
||||
_timerSchedulerNet = null;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给Session会话增加漫游功能
|
||||
/// 如果指定的roamingId已经存在漫游,会把这个漫游功能和当前Session会话关联起来。
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="roamingId">自定义roamingId,这个Id在漫游中并没有实际作用,但用户可以用这个id来进行标记。。</param>
|
||||
/// <param name="isAutoDispose">是否在Session断开的时候自动断开漫游功能。</param>
|
||||
/// <param name="delayRemove">如果开启了自定断开漫游功能需要设置一个延迟多久执行断开。</param>
|
||||
/// <returns>创建成功会返回SessionRoamingComponent组件,这个组件提供漫游的所有功能。</returns>
|
||||
public SessionRoamingComponent Create(Session session, long roamingId, bool isAutoDispose, int delayRemove)
|
||||
{
|
||||
if (session.SessionRoamingComponent != null)
|
||||
{
|
||||
if (session.SessionRoamingComponent.Id != roamingId)
|
||||
{
|
||||
Log.Error("The current session has created a SessionRoamingComponent.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_sessionRoamingComponents.TryGetValue(roamingId, out var sessionRoamingComponent))
|
||||
{
|
||||
sessionRoamingComponent = Entity.Create<SessionRoamingComponent>(Scene, roamingId, true, true);
|
||||
sessionRoamingComponent.Initialize(session);
|
||||
_sessionRoamingComponents.Add(roamingId, sessionRoamingComponent);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAutoDispose)
|
||||
{
|
||||
session.AddComponent<SessionRoamingFlgComponent>(roamingId).DelayRemove = delayRemove;
|
||||
}
|
||||
|
||||
return session.SessionRoamingComponent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Session会话的漫游组件
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <returns></returns>
|
||||
public SessionRoamingComponent Get(Session session)
|
||||
{
|
||||
var sessionRoamingFlgComponent = session.GetComponent<SessionRoamingFlgComponent>();
|
||||
|
||||
if (sessionRoamingFlgComponent != null)
|
||||
{
|
||||
if (_sessionRoamingComponents.TryGetValue(sessionRoamingFlgComponent.Id, out var sessionRoamingComponent))
|
||||
{
|
||||
return sessionRoamingComponent;
|
||||
}
|
||||
|
||||
Log.Error($"There is no SessionRoamingComponent with roamingId: {sessionRoamingFlgComponent.Id}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("The current session has not created a roaming session yet, so you need to create one first.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Session会话的漫游组件
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="sessionRoamingComponent"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGet(Session session, out SessionRoamingComponent sessionRoamingComponent)
|
||||
{
|
||||
var sessionRoamingFlgComponent = session.GetComponent<SessionRoamingFlgComponent>();
|
||||
|
||||
if (sessionRoamingFlgComponent != null)
|
||||
{
|
||||
return _sessionRoamingComponents.TryGetValue(sessionRoamingFlgComponent.Id, out sessionRoamingComponent);
|
||||
}
|
||||
|
||||
sessionRoamingComponent = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一个漫游
|
||||
/// </summary>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <param name="roamingType">要移除的RoamingType,默认不设置是移除所有漫游。</param>
|
||||
/// <param name="delayRemove">当设置了延迟移除时间后,会在设置的时间后再进行移除。</param>
|
||||
public async FTask Remove(long roamingId, int roamingType, int delayRemove = 0)
|
||||
{
|
||||
if (_delayRemoveTaskId.Remove(roamingId, out var taskId))
|
||||
{
|
||||
_timerSchedulerNet.Remove(taskId);
|
||||
}
|
||||
|
||||
if (delayRemove <= 0)
|
||||
{
|
||||
await InnerRemove(roamingId, roamingType);
|
||||
return;
|
||||
}
|
||||
|
||||
taskId = _timerSchedulerNet.OnceTimer(delayRemove, () =>
|
||||
{ InnerRemove(roamingId, roamingType).Coroutine(); });
|
||||
_delayRemoveTaskId.Add(roamingId, taskId);
|
||||
}
|
||||
|
||||
private async FTask InnerRemove(long roamingId, int roamingType)
|
||||
{
|
||||
if (!_sessionRoamingComponents.Remove(roamingId, out var sessionRoamingComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await sessionRoamingComponent.UnLink(roamingType);
|
||||
sessionRoamingComponent.Dispose();
|
||||
_delayRemoveTaskId.Remove(roamingId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 漫游Roaming帮助类
|
||||
/// </summary>
|
||||
public static class RoamingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 给Session会话增加漫游功能
|
||||
/// 如果指定的roamingId已经存在漫游,会把这个漫游功能和当前Session会话关联起来。
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="roamingId">自定义roamingId,这个Id在漫游中并没有实际作用,但用户可以用这个id来进行标记。</param>
|
||||
/// <param name="isAutoDispose">是否在Session断开的时候自动断开漫游功能。</param>
|
||||
/// <param name="delayRemove">如果开启了自定断开漫游功能需要设置一个延迟多久执行断开。</param>
|
||||
/// <returns>创建成功会返回SessionRoamingComponent组件,这个组件提供漫游的所有功能。</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SessionRoamingComponent CreateRoaming(this Session session, long roamingId, bool isAutoDispose = true, int delayRemove = 1000 * 60 * 3)
|
||||
{
|
||||
return session.Scene.RoamingComponent.Create(session, roamingId, isAutoDispose, delayRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Session会话的漫游组件
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SessionRoamingComponent GetRoaming(this Session session)
|
||||
{
|
||||
return session.Scene.RoamingComponent.Get(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前Session会话的漫游组件
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="sessionRoamingComponent"></param>
|
||||
/// <returns>如果返回为false表示没有获取到漫游组件。</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetRoaming(this Session session, out SessionRoamingComponent sessionRoamingComponent)
|
||||
{
|
||||
return session.Scene.RoamingComponent.TryGet(session, out sessionRoamingComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一个漫游
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="roamingType">要移除的RoamingType,默认不设置是移除所有漫游。</param>
|
||||
/// <param name="delayRemove">当设置了延迟移除时间后,会在设置的时间后再进行移除。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static async FTask RemoveRoaming(this Session session, int roamingType = 0, int delayRemove = 0)
|
||||
{
|
||||
if (session.SessionRoamingComponent == null || session.SessionRoamingComponent.Id == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await session.Scene.RoamingComponent.Remove(session.SessionRoamingComponent.Id, roamingType, delayRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一个漫游
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <param name="roamingType">要移除的RoamingType,默认不设置是移除所有漫游。</param>
|
||||
/// <param name="delayRemove">当设置了延迟移除时间后,会在设置的时间后再进行移除。</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static async FTask RemoveRoaming(Scene scene, long roamingId, int roamingType = 0, int delayRemove = 0)
|
||||
{
|
||||
if (roamingId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await scene.RoamingComponent.Remove(roamingId, roamingType, delayRemove);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,279 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Platform.Net;
|
||||
using Fantasy.Scheduler;
|
||||
using Fantasy.Timer;
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
internal sealed class SessionRoamingComponentDestroySystem : DestroySystem<SessionRoamingComponent>
|
||||
{
|
||||
protected override void Destroy(SessionRoamingComponent self)
|
||||
{
|
||||
self.RoamingLock.Dispose();
|
||||
self.RoamingMessageLock.Dispose();
|
||||
|
||||
self.RoamingLock = null;
|
||||
self.RoamingMessageLock = null;
|
||||
self.TimerComponent = null;
|
||||
self.NetworkMessagingComponent = null;
|
||||
self.MessageDispatcherComponent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session的漫游组件。
|
||||
/// 用于关联对应的Session的功能。
|
||||
/// 但这个组件并不会挂载到这个Session下。
|
||||
/// </summary>
|
||||
public sealed class SessionRoamingComponent : Entity
|
||||
{
|
||||
internal CoroutineLock RoamingLock;
|
||||
internal CoroutineLock RoamingMessageLock;
|
||||
internal TimerComponent TimerComponent;
|
||||
internal NetworkMessagingComponent NetworkMessagingComponent;
|
||||
internal MessageDispatcherComponent MessageDispatcherComponent;
|
||||
/// <summary>
|
||||
/// 漫游的列表。
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, Roaming> _roaming = new Dictionary<int, Roaming>();
|
||||
|
||||
internal void Initialize(Session session)
|
||||
{
|
||||
session.SessionRoamingComponent = this;
|
||||
|
||||
var scene = session.Scene;
|
||||
TimerComponent = scene.TimerComponent;
|
||||
NetworkMessagingComponent = scene.NetworkMessagingComponent;
|
||||
MessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
RoamingLock = scene.CoroutineLockComponent.Create(this.GetType().TypeHandle.Value.ToInt64());
|
||||
RoamingMessageLock = scene.CoroutineLockComponent.Create(this.GetType().TypeHandle.Value.ToInt64());
|
||||
}
|
||||
|
||||
#region Get
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取一个漫游。
|
||||
/// </summary>
|
||||
/// <param name="roamingType"></param>
|
||||
/// <param name="roaming"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetRoaming(int roamingType, out Roaming roaming)
|
||||
{
|
||||
return _roaming.TryGetValue(roamingType, out roaming);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Link
|
||||
|
||||
/// <summary>
|
||||
/// 建立漫游关系。
|
||||
/// </summary>
|
||||
/// <param name="session">要建立漫游协议的目标Scene的SceneConfig。</param>
|
||||
/// <param name="targetSceneConfig">需要转发的Session</param>
|
||||
/// <param name="roamingTyp">要创建的漫游协议类型。</param>
|
||||
/// <returns>如果建立完成会返回为0,其余不为0的都是发生错误了。可以通过InnerErrorCode.cs来查看错误。</returns>
|
||||
public async FTask<uint> Link(Session session, SceneConfig targetSceneConfig, int roamingTyp)
|
||||
{
|
||||
return await Link(targetSceneConfig.RouteId, session.RuntimeId, roamingTyp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立漫游关系。
|
||||
/// </summary>
|
||||
/// <param name="targetSceneRouteId">要建立漫游协议的目标Scene的RouteId。</param>
|
||||
/// <param name="forwardSessionRouteId">需要转发的Session的RouteId。</param>
|
||||
/// <param name="roamingType">要创建的漫游协议类型。</param>
|
||||
/// <returns>如果建立完成会返回为0,其余不为0的都是发生错误了。可以通过InnerErrorCode.cs来查看错误。</returns>
|
||||
public async FTask<uint> Link(long targetSceneRouteId, long forwardSessionRouteId, int roamingType)
|
||||
{
|
||||
if (_roaming.ContainsKey(roamingType))
|
||||
{
|
||||
return InnerErrorCode.ErrLinkRoamingAlreadyExists;
|
||||
}
|
||||
|
||||
var response = (I_LinkRoamingResponse)await Scene.NetworkMessagingComponent.CallInnerRoute(targetSceneRouteId,
|
||||
new I_LinkRoamingRequest()
|
||||
{
|
||||
RoamingId = Id,
|
||||
RoamingType = roamingType,
|
||||
ForwardSessionRouteId = forwardSessionRouteId,
|
||||
SceneRouteId = Scene.RuntimeId
|
||||
});
|
||||
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
return response.ErrorCode;
|
||||
}
|
||||
|
||||
var roaming = Entity.Create<Roaming>(Scene, true, true);
|
||||
roaming.TerminusId = response.TerminusId;
|
||||
roaming.TargetSceneRouteId = targetSceneRouteId;
|
||||
roaming.ForwardSessionRouteId = forwardSessionRouteId;
|
||||
roaming.SessionRoamingComponent = this;
|
||||
roaming.RoamingType = roamingType;
|
||||
roaming.RoamingLock = RoamingLock;
|
||||
_roaming.Add(roamingType, roaming);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UnLink
|
||||
|
||||
/// <summary>
|
||||
/// 断开当前的所有漫游关系。
|
||||
/// <param name="removeRoamingType">要移除的RoamingType,默认不设置是移除所有漫游。</param>
|
||||
/// </summary>
|
||||
public async FTask UnLink(int removeRoamingType = 0)
|
||||
{
|
||||
switch (removeRoamingType)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
foreach (var (roamingType,roaming) in _roaming)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorCode = await roaming.Disconnect();
|
||||
|
||||
if (errorCode != 0)
|
||||
{
|
||||
Log.Warning($"roaming roamingId:{Id} roamingType:{roamingType} disconnect errorCode:{errorCode}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
roaming.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_roaming.Clear();
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!_roaming.Remove(removeRoamingType, out var roaming))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var errorCode = await roaming.Disconnect();
|
||||
|
||||
if (errorCode != 0)
|
||||
{
|
||||
Log.Warning($"roaming roamingId:{Id} roamingType:{removeRoamingType} disconnect errorCode:{errorCode}");
|
||||
}
|
||||
|
||||
roaming.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Message
|
||||
|
||||
internal async FTask Send(int roamingType, Type requestType, APackInfo packInfo)
|
||||
{
|
||||
await Call(roamingType, requestType, packInfo);
|
||||
}
|
||||
|
||||
internal async FTask<IResponse> Call(int roamingType, Type requestType, APackInfo packInfo)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
|
||||
packInfo.IsDisposed = true;
|
||||
|
||||
if (!_roaming.TryGetValue(roamingType, out var roaming))
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
|
||||
var failCount = 0;
|
||||
var runtimeId = RuntimeId;
|
||||
var routeId = roaming.TerminusId;
|
||||
IResponse iRouteResponse = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (await RoamingMessageLock.Wait(roamingType, "RoamingComponent Call MemoryStream"))
|
||||
{
|
||||
while (!IsDisposed)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
routeId = await roaming.GetTerminusId();
|
||||
}
|
||||
|
||||
if (routeId == 0)
|
||||
{
|
||||
return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
|
||||
iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(routeId, requestType, packInfo);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRoamingTimeout;
|
||||
}
|
||||
|
||||
switch (iRouteResponse.ErrorCode)
|
||||
{
|
||||
case InnerErrorCode.ErrRouteTimeout:
|
||||
case InnerErrorCode.ErrRoamingTimeout:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
case InnerErrorCode.ErrNotFoundRoute:
|
||||
case InnerErrorCode.ErrNotFoundRoaming:
|
||||
{
|
||||
if (++failCount > 20)
|
||||
{
|
||||
Log.Error($"RoamingComponent.Call failCount > 20 route send message fail, LinkRoamingId: {routeId}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
await TimerComponent.Net.WaitAsync(100);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrNotFoundRoaming;
|
||||
}
|
||||
routeId = 0;
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
packInfo.Dispose();
|
||||
}
|
||||
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
internal sealed class SessionRoamingFlgComponent : Entity
|
||||
{
|
||||
public int DelayRemove;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
DisposeAsync().Coroutine();
|
||||
}
|
||||
|
||||
private async FTask DisposeAsync()
|
||||
{
|
||||
var roamingId = Id;
|
||||
await Scene.RoamingComponent.Remove(roamingId, 0, DelayRemove);
|
||||
DelayRemove = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.DataStructure.Dictionary;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Network;
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
/// <summary>
|
||||
/// 当Terminus创建完成后发送的事件参数
|
||||
/// </summary>
|
||||
public struct OnCreateTerminus
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取与事件关联的场景实体。
|
||||
/// </summary>
|
||||
public readonly Scene Scene;
|
||||
/// <summary>
|
||||
/// 获取与事件关联的Terminus。
|
||||
/// </summary>
|
||||
public readonly Terminus Terminus;
|
||||
/// <summary>
|
||||
/// 初始化一个新的 OnCreateTerminus 实例。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="terminus"></param>
|
||||
public OnCreateTerminus(Scene scene, Terminus terminus)
|
||||
{
|
||||
Scene = scene;
|
||||
Terminus = terminus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 漫游终端管理组件。
|
||||
/// 这个组件不需要手动挂载,会在Scene启动的时候自动挂载这个组件。
|
||||
/// </summary>
|
||||
public sealed class TerminusComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 漫游终端的实体集合。
|
||||
/// </summary>
|
||||
private readonly Dictionary<long, Terminus> _terminals = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dispose
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var (_, terminus) in _terminals)
|
||||
{
|
||||
terminus.Dispose();
|
||||
}
|
||||
|
||||
_terminals.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的漫游终端。
|
||||
/// </summary>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <param name="roamingType"></param>
|
||||
/// <param name="forwardSessionRouteId"></param>
|
||||
/// <param name="forwardSceneRouteId"></param>
|
||||
/// <returns></returns>
|
||||
public async FTask<(uint, Terminus)> Create(long roamingId, int roamingType, long forwardSessionRouteId, long forwardSceneRouteId)
|
||||
{
|
||||
if (_terminals.ContainsKey(roamingId))
|
||||
{
|
||||
return (InnerErrorCode.ErrAddRoamingTerminalAlreadyExists, null);
|
||||
}
|
||||
|
||||
var terminus = roamingId == 0
|
||||
? Entity.Create<Terminus>(Scene, false, true)
|
||||
: Entity.Create<Terminus>(Scene, roamingId, false, true);
|
||||
terminus.RoamingType = roamingType;
|
||||
terminus.TerminusId = terminus.RuntimeId;
|
||||
terminus.ForwardSceneRouteId = forwardSceneRouteId;
|
||||
terminus.ForwardSessionRouteId = forwardSessionRouteId;
|
||||
terminus.RoamingMessageLock = Scene.CoroutineLockComponent.Create(terminus.Type.TypeHandle.Value.ToInt64());
|
||||
await Scene.EventComponent.PublishAsync(new OnCreateTerminus(Scene, terminus));
|
||||
_terminals.Add(terminus.Id, terminus);
|
||||
return (0U, terminus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个漫游终端。
|
||||
/// </summary>
|
||||
/// <param name="terminus"></param>
|
||||
public void AddTerminus(Terminus terminus)
|
||||
{
|
||||
_terminals.Add(terminus.Id, terminus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据roamingId获取一个漫游终端。
|
||||
/// </summary>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <param name="terminus"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetTerminus(long roamingId, out Terminus terminus)
|
||||
{
|
||||
return _terminals.TryGetValue(roamingId, out terminus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据roamingId获取一个漫游终端。
|
||||
/// </summary>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <returns></returns>
|
||||
public Terminus GetTerminus(long roamingId)
|
||||
{
|
||||
return _terminals[roamingId];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据roamingId移除一个漫游终端。
|
||||
/// </summary>
|
||||
/// <param name="roamingId"></param>
|
||||
/// <param name="isDispose"></param>
|
||||
public void RemoveTerminus(long roamingId, bool isDispose = true)
|
||||
{
|
||||
if (!_terminals.Remove(roamingId, out var terminus))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDispose)
|
||||
{
|
||||
terminus.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.InnerMessage;
|
||||
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
/// <summary>
|
||||
/// 漫游实体
|
||||
/// </summary>
|
||||
public sealed class Roaming : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接到漫游TerminusId。
|
||||
/// 也可以理解为目标实体的RouteId。
|
||||
/// </summary>
|
||||
internal long TerminusId;
|
||||
/// <summary>
|
||||
/// 漫游目标Scene的RouteId。
|
||||
/// </summary>
|
||||
public long TargetSceneRouteId { get; internal set; }
|
||||
/// <summary>
|
||||
/// 漫游转发Session的RouteId。
|
||||
/// </summary>
|
||||
public long ForwardSessionRouteId { get; internal set; }
|
||||
/// <summary>
|
||||
/// 当前漫游类型。
|
||||
/// </summary>
|
||||
public int RoamingType { get; internal set; }
|
||||
/// <summary>
|
||||
/// 协程锁。
|
||||
/// </summary>
|
||||
internal CoroutineLock RoamingLock;
|
||||
/// <summary>
|
||||
/// 当前正在执行的协程锁。
|
||||
/// </summary>
|
||||
private WaitCoroutineLock? _waitCoroutineLock;
|
||||
/// <summary>
|
||||
/// 关联Session的漫游组件。
|
||||
/// </summary>
|
||||
internal SessionRoamingComponent SessionRoamingComponent;
|
||||
/// <summary>
|
||||
/// 获得当前漫游对应终端的TerminusId。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal async FTask<long> GetTerminusId()
|
||||
{
|
||||
using (await RoamingLock.Wait(RoamingType,"Roaming.cs GetTerminusId"))
|
||||
{
|
||||
return TerminusId;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置当前漫游对应的终端的TerminusId。
|
||||
/// </summary>
|
||||
/// <param name="terminusId"></param>
|
||||
internal async FTask SetTerminusId(long terminusId)
|
||||
{
|
||||
using (await RoamingLock.Wait(RoamingType,"Roaming.cs SetTerminusId"))
|
||||
{
|
||||
TerminusId = terminusId;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 锁定TerminusId。
|
||||
/// </summary>
|
||||
internal async FTask LockTerminusId()
|
||||
{
|
||||
_waitCoroutineLock = await RoamingLock.Wait(RoamingType,"Roaming.cs LockTerminusId");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解锁TerminusId。
|
||||
/// </summary>
|
||||
/// <param name="terminusId"></param>
|
||||
/// <param name="targetSceneRouteId"></param>
|
||||
internal void UnLockTerminusId(long terminusId, long targetSceneRouteId)
|
||||
{
|
||||
if (_waitCoroutineLock == null)
|
||||
{
|
||||
Log.Error("terminusId unlock waitCoroutineLock is null");
|
||||
return;
|
||||
}
|
||||
|
||||
TerminusId = terminusId;
|
||||
TargetSceneRouteId = targetSceneRouteId;
|
||||
_waitCoroutineLock.Dispose();
|
||||
_waitCoroutineLock = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开当前漫游的连接。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask<uint> Disconnect()
|
||||
{
|
||||
var response =
|
||||
await Scene.NetworkMessagingComponent.CallInnerRoute(TargetSceneRouteId, new I_UnLinkRoamingRequest()
|
||||
{
|
||||
RoamingId = SessionRoamingComponent.Id
|
||||
});
|
||||
return response.ErrorCode;
|
||||
}
|
||||
/// <summary>
|
||||
/// 销毁方法
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_waitCoroutineLock != null)
|
||||
{
|
||||
_waitCoroutineLock.Dispose();
|
||||
_waitCoroutineLock = null;
|
||||
}
|
||||
|
||||
TerminusId = 0;
|
||||
TargetSceneRouteId = 0;
|
||||
ForwardSessionRouteId = 0;
|
||||
|
||||
RoamingLock = null;
|
||||
SessionRoamingComponent = null;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,338 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Scheduler;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
// ReSharper disable UnassignedField.Global
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
namespace Fantasy.Network.Roaming;
|
||||
|
||||
/// <summary>
|
||||
/// 漫游终端实体
|
||||
/// </summary>
|
||||
public sealed class Terminus : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前漫游终端的TerminusId。
|
||||
/// 可以通过 TerminusId 发送消息给这个漫游终端。
|
||||
/// 也可以理解为实体的RuntimeId。
|
||||
/// </summary>
|
||||
internal long TerminusId;
|
||||
/// <summary>
|
||||
/// 当前漫游终端的类型。
|
||||
/// </summary>
|
||||
[BsonElement("r")]
|
||||
internal int RoamingType;
|
||||
/// <summary>
|
||||
/// 漫游转发Session所在的Scene的RouteId。
|
||||
/// </summary>
|
||||
[BsonElement("s")]
|
||||
internal long ForwardSceneRouteId;
|
||||
/// <summary>
|
||||
/// 漫游转发Session的RouteId。
|
||||
/// 不知道原理千万不要手动赋值这个。
|
||||
/// </summary>
|
||||
[BsonElement("f")]
|
||||
internal long ForwardSessionRouteId;
|
||||
/// <summary>
|
||||
/// 关联的玩家实体
|
||||
/// </summary>
|
||||
[BsonElement("e")]
|
||||
public Entity TerminusEntity;
|
||||
/// <summary>
|
||||
/// 漫游消息锁。
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
internal CoroutineLock RoamingMessageLock;
|
||||
/// <summary>
|
||||
/// 获得转发的SessionRouteId,可以通过这个Id来发送消息来自动转发到客户端。
|
||||
/// </summary>
|
||||
public long SessionRouteId => ForwardSessionRouteId;
|
||||
/// <summary>
|
||||
/// 存放其他漫游终端的Id。
|
||||
/// 通过这个Id可以发送消息给它。
|
||||
/// </summary>
|
||||
[BsonIgnore]
|
||||
private readonly Dictionary<int, long> _roamingTerminusId = new Dictionary<int, long>();
|
||||
/// <summary>
|
||||
/// 创建关联的终端实体。
|
||||
/// 创建完成后,接收消息都是由关联的终端实体来处理。
|
||||
/// 注意,当你销毁这个实体的时候,并不能直接销毁Terminus,会导致无法接收到漫游消息。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T LinkTerminusEntity<T>() where T : Entity, new()
|
||||
{
|
||||
if (TerminusEntity != null)
|
||||
{
|
||||
Log.Error($"TerminusEntity:{TerminusEntity.Type.FullName} Already exists!");
|
||||
return null;
|
||||
}
|
||||
|
||||
var t = Entity.Create<T>(Scene, true, true);
|
||||
TerminusEntity = t;
|
||||
TerminusId = TerminusEntity.RuntimeId;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联的终端实体。
|
||||
/// 注意,当你销毁这个实体的时候,并不能直接销毁Terminus,会导致无法接收到漫游消息
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public void LinkTerminusEntity(Entity entity)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
Log.Error("Entity cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TerminusEntity != null)
|
||||
{
|
||||
Log.Error($"TerminusEntity:{TerminusEntity.Type.FullName} Already exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
TerminusEntity = entity;
|
||||
TerminusId = TerminusEntity.RuntimeId;
|
||||
}
|
||||
|
||||
#region Transfer
|
||||
|
||||
/// <summary>
|
||||
/// 传送漫游终端
|
||||
/// 传送完成后,漫游终端和关联的玩家实体都会被销毁。
|
||||
/// 所以如果有其他组件关联这个实体,要提前记录好Id,方便传送后清理。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask<uint> StartTransfer(long targetSceneRouteId)
|
||||
{
|
||||
var currentSceneRouteId = Scene.SceneConfig.RouteId;
|
||||
if (targetSceneRouteId == currentSceneRouteId)
|
||||
{
|
||||
Log.Warning($"Unable to teleport to your own scene targetSceneRouteId:{targetSceneRouteId} == currentSceneRouteId:{currentSceneRouteId}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 传送目标服务器之前要先锁定,防止再传送过程中还有其他消息发送过来。
|
||||
var lockErrorCode = await Lock();
|
||||
if (lockErrorCode != 0)
|
||||
{
|
||||
return lockErrorCode;
|
||||
}
|
||||
// 开始执行传送请求。
|
||||
var response = (I_TransferTerminusResponse)await Scene.NetworkMessagingComponent.CallInnerRoute(
|
||||
targetSceneRouteId,
|
||||
new I_TransferTerminusRequest()
|
||||
{
|
||||
Terminus = this
|
||||
});
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
// 如果传送出现异常,需要先解锁,不然会出现一直卡死的问题。
|
||||
await UnLock();
|
||||
return response.ErrorCode;
|
||||
}
|
||||
// 在当前Scene下移除漫游终端。
|
||||
Scene.TerminusComponent.RemoveTerminus(Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
// 如果代码执行出现任何异常,要先去解锁,避免会出现卡死的问题。
|
||||
await UnLock();
|
||||
return InnerErrorCode.ErrTerminusStartTransfer;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传送完成。
|
||||
/// 当传送完成后,需要清理漫游终端。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask<uint> TransferComplete(Scene scene)
|
||||
{
|
||||
// 首先恢复漫游终端的序列化数据。并且注册到框架中。
|
||||
Deserialize(scene);
|
||||
TerminusId = RuntimeId;
|
||||
if (TerminusEntity != null)
|
||||
{
|
||||
TerminusEntity.Deserialize(scene);
|
||||
TerminusId = TerminusEntity.RuntimeId;
|
||||
}
|
||||
// 然后要解锁下漫游
|
||||
return await UnLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定漫游当执行锁定了后,所有消息都会被暂时放入队列中不会发送。
|
||||
/// 必须要解锁后才能继续发送消息。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask<uint> Lock()
|
||||
{
|
||||
var response = await Scene.NetworkMessagingComponent.CallInnerRoute(ForwardSceneRouteId,
|
||||
new I_LockTerminusIdRequest()
|
||||
{
|
||||
SessionRuntimeId = ForwardSessionRouteId,
|
||||
RoamingType = RoamingType
|
||||
});
|
||||
return response.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁定漫游
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask<uint> UnLock()
|
||||
{
|
||||
var response = await Scene.NetworkMessagingComponent.CallInnerRoute(ForwardSceneRouteId,
|
||||
new I_UnLockTerminusIdRequest()
|
||||
{
|
||||
SessionRuntimeId = ForwardSessionRouteId,
|
||||
RoamingType = RoamingType,
|
||||
TerminusId = TerminusId,
|
||||
TargetSceneRouteId = Scene.RouteId
|
||||
});
|
||||
return response.ErrorCode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Message
|
||||
|
||||
private async FTask<long> GetTerminusId(int roamingType)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var response = (I_GetTerminusIdResponse)await Scene.NetworkMessagingComponent.CallInnerRoute(
|
||||
ForwardSceneRouteId,
|
||||
new I_GetTerminusIdRequest()
|
||||
{
|
||||
SessionRuntimeId = ForwardSessionRouteId,
|
||||
RoamingType = roamingType
|
||||
});
|
||||
return response.TerminusId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息给客户端
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Send(IRouteMessage message)
|
||||
{
|
||||
Scene.NetworkMessagingComponent.SendInnerRoute(ForwardSessionRouteId, message);
|
||||
}
|
||||
/// <summary>
|
||||
/// 发送一个漫游消息
|
||||
/// </summary>
|
||||
/// <param name="roamingType"></param>
|
||||
/// <param name="message"></param>
|
||||
public void Send(int roamingType, IRoamingMessage message)
|
||||
{
|
||||
Call(roamingType, message).Coroutine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个漫游RPC消息。
|
||||
/// </summary>
|
||||
/// <param name="roamingType"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public async FTask<IResponse> Call(int roamingType, IRoamingMessage request)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
|
||||
if (roamingType == RoamingType)
|
||||
{
|
||||
Log.Warning($"Does not support sending messages to the same scene as roamingType currentRoamingType:{RoamingType} roamingType:{roamingType}");
|
||||
return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
|
||||
var failCount = 0;
|
||||
var runtimeId = RuntimeId;
|
||||
IResponse iRouteResponse = null;
|
||||
_roamingTerminusId.TryGetValue(roamingType, out var routeId);
|
||||
|
||||
using (await RoamingMessageLock.Wait(roamingType, "Terminus Call request"))
|
||||
{
|
||||
while (!IsDisposed)
|
||||
{
|
||||
if (routeId == 0)
|
||||
{
|
||||
routeId = await GetTerminusId(roamingType);
|
||||
|
||||
if (routeId != 0)
|
||||
{
|
||||
_roamingTerminusId[roamingType] = routeId;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming);
|
||||
}
|
||||
}
|
||||
|
||||
iRouteResponse = await Scene.NetworkMessagingComponent.CallInnerRoute(routeId, request);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrRoamingTimeout;
|
||||
}
|
||||
|
||||
switch (iRouteResponse.ErrorCode)
|
||||
{
|
||||
case InnerErrorCode.ErrRouteTimeout:
|
||||
case InnerErrorCode.ErrRoamingTimeout:
|
||||
{
|
||||
return iRouteResponse;
|
||||
}
|
||||
case InnerErrorCode.ErrNotFoundRoute:
|
||||
case InnerErrorCode.ErrNotFoundRoaming:
|
||||
{
|
||||
if (++failCount > 20)
|
||||
{
|
||||
Log.Error($"Terminus.Call failCount > 20 route send message fail, TerminusId: {routeId}");
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
await Scene.TimerComponent.Net.WaitAsync(100);
|
||||
|
||||
if (runtimeId != RuntimeId)
|
||||
{
|
||||
iRouteResponse.ErrorCode = InnerErrorCode.ErrNotFoundRoaming;
|
||||
}
|
||||
|
||||
routeId = 0;
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iRouteResponse;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
internal sealed class I_GetTerminusIdRequestHandler : RouteRPC<Scene, I_GetTerminusIdRequest, I_GetTerminusIdResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_GetTerminusIdRequest request, I_GetTerminusIdResponse response, Action reply)
|
||||
{
|
||||
if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundSession;
|
||||
return;
|
||||
}
|
||||
|
||||
var session = (Session)sessionEntity;
|
||||
|
||||
if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType;
|
||||
return;
|
||||
}
|
||||
|
||||
response.TerminusId = await sessionRoaming.GetTerminusId();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
public sealed class I_LinkRoamingRequestHandler : RouteRPC<Scene, I_LinkRoamingRequest, I_LinkRoamingResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_LinkRoamingRequest request, I_LinkRoamingResponse response, Action reply)
|
||||
{
|
||||
var (errorCode, roamingTerminal) = await scene.TerminusComponent.Create(
|
||||
request.RoamingId, request.RoamingType,
|
||||
request.ForwardSessionRouteId, request.SceneRouteId);
|
||||
|
||||
if (errorCode != 0)
|
||||
{
|
||||
response.ErrorCode = errorCode;
|
||||
return;
|
||||
}
|
||||
|
||||
response.TerminusId = roamingTerminal.TerminusId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
/// <summary>
|
||||
/// 内部网络漫游锁定的请求处理。
|
||||
/// </summary>
|
||||
internal sealed class I_LockTerminusIdRequestHandler : RouteRPC<Scene, I_LockTerminusIdRequest, I_LockTerminusIdResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_LockTerminusIdRequest request, I_LockTerminusIdResponse response, Action reply)
|
||||
{
|
||||
if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundSession;
|
||||
return;
|
||||
}
|
||||
|
||||
var session = (Session)sessionEntity;
|
||||
|
||||
if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType;
|
||||
return;
|
||||
}
|
||||
|
||||
await sessionRoaming.LockTerminusId();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
/// <summary>
|
||||
/// 传送漫游Terminus的请求处理
|
||||
/// </summary>
|
||||
internal sealed class I_TransferTerminusRequestHandler : RouteRPC<Scene, I_TransferTerminusRequest, I_TransferTerminusResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_TransferTerminusRequest request, I_TransferTerminusResponse response, Action reply)
|
||||
{
|
||||
// 添加Terminus到当前Scene下。
|
||||
scene.TerminusComponent.AddTerminus(request.Terminus);
|
||||
// 执行Terminus的传送完成逻辑。
|
||||
await request.Terminus.TransferComplete(scene);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
internal sealed class I_UnLinkRoamingRequestHandler : RouteRPC<Scene, I_UnLinkRoamingRequest, I_UnLinkRoamingResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_UnLinkRoamingRequest request, I_UnLinkRoamingResponse response, Action reply)
|
||||
{
|
||||
scene.TerminusComponent.RemoveTerminus(request.RoamingId, request.DisposeRoaming);
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Roaming.Handler;
|
||||
|
||||
/// <summary>
|
||||
/// 内部网络漫游解锁的请求处理。
|
||||
/// </summary>
|
||||
internal sealed class I_UnLockTerminusIdRequestHandler : RouteRPC<Scene, I_UnLockTerminusIdRequest, I_UnLockTerminusIdResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, I_UnLockTerminusIdRequest request, I_UnLockTerminusIdResponse response, Action reply)
|
||||
{
|
||||
if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrUnLockTerminusIdNotFoundSession;
|
||||
return;
|
||||
}
|
||||
|
||||
var session = (Session)sessionEntity;
|
||||
|
||||
if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming))
|
||||
{
|
||||
response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType;
|
||||
return;
|
||||
}
|
||||
|
||||
sessionRoaming.UnLockTerminusId(request.TerminusId, request.TargetSceneRouteId);
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network;
|
||||
|
||||
/// <summary>
|
||||
/// RouteComponent的AwakeSystem
|
||||
/// </summary>
|
||||
public sealed class RouteComponentAwakeSystem : AwakeSystem<RouteComponent>
|
||||
{
|
||||
/// <summary>
|
||||
/// Awake
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
protected override void Awake(RouteComponent self)
|
||||
{
|
||||
((Session)self.Parent).RouteComponent = self;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义Route组件、如果要自定义Route协议必须使用这个组件
|
||||
/// </summary>
|
||||
public sealed class RouteComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储路由类型和路由ID的映射关系。
|
||||
/// </summary>
|
||||
public readonly Dictionary<long, long> RouteAddress = new Dictionary<long, long>();
|
||||
|
||||
/// <summary>
|
||||
/// 添加路由类型和路由ID的映射关系。
|
||||
/// </summary>
|
||||
/// <param name="routeType">路由类型。</param>
|
||||
/// <param name="routeId">路由ID。</param>
|
||||
public void AddAddress(long routeType, long routeId)
|
||||
{
|
||||
RouteAddress.Add(routeType, routeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定路由类型的映射关系。
|
||||
/// </summary>
|
||||
/// <param name="routeType">路由类型。</param>
|
||||
public void RemoveAddress(long routeType)
|
||||
{
|
||||
RouteAddress.Remove(routeType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路由类型的路由ID。
|
||||
/// </summary>
|
||||
/// <param name="routeType">路由类型。</param>
|
||||
/// <returns>路由ID。</returns>
|
||||
public long GetRouteId(long routeType)
|
||||
{
|
||||
return RouteAddress.GetValueOrDefault(routeType, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定路由类型的路由ID。
|
||||
/// </summary>
|
||||
/// <param name="routeType">路由类型。</param>
|
||||
/// <param name="routeId">输出的路由ID。</param>
|
||||
/// <returns>如果获取成功返回true,否则返回false。</returns>
|
||||
public bool TryGetRouteId(long routeType, out long routeId)
|
||||
{
|
||||
return RouteAddress.TryGetValue(routeType, out routeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放组件资源,清空映射关系。
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
RouteAddress.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,156 @@
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
#if FANTASY_CONSOLE
|
||||
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Timer;
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
public class SessionHeartbeatComponentAwakeSystem : AwakeSystem<SessionHeartbeatComponent>
|
||||
{
|
||||
protected override void Awake(SessionHeartbeatComponent self)
|
||||
{
|
||||
self.TimerComponent = self.Scene.TimerComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 负责管理会话心跳的组件。
|
||||
/// </summary>
|
||||
public class SessionHeartbeatComponent : Entity
|
||||
{
|
||||
public int TimeOut;
|
||||
public long TimerId;
|
||||
public long LastTime;
|
||||
public long SelfRunTimeId;
|
||||
public long TimeOutTimerId;
|
||||
public long SessionRunTimeId;
|
||||
public TimerComponent TimerComponent;
|
||||
public EntityReference<Session> Session;
|
||||
private readonly PingRequest _pingRequest = new PingRequest();
|
||||
|
||||
public int Ping { get; private set; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
Ping = 0;
|
||||
Session = null;
|
||||
TimeOut = 0;
|
||||
SelfRunTimeId = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的间隔启动心跳功能。
|
||||
/// </summary>
|
||||
/// <param name="interval">以毫秒为单位的心跳请求发送间隔。</param>
|
||||
/// <param name="timeOut">设置与服务器的通信超时时间,如果超过这个时间限制,将自动断开会话(Session)。</param>
|
||||
/// <param name="timeOutInterval">用于检测与服务器连接超时频率。</param>
|
||||
public void Start(int interval, int timeOut = 2000, int timeOutInterval = 3000)
|
||||
{
|
||||
TimeOut = timeOut + interval;
|
||||
Session = (Session)Parent;
|
||||
SelfRunTimeId = RuntimeId;
|
||||
LastTime = TimeHelper.Now;
|
||||
|
||||
if (TimerComponent == null)
|
||||
{
|
||||
Log.Error("请在Unity的菜单执行Fantasy->Generate link.xml再重新打包");
|
||||
return;
|
||||
}
|
||||
|
||||
TimerId = TimerComponent.Net.RepeatedTimer(interval, () => RepeatedSend().Coroutine());
|
||||
TimeOutTimerId = TimerComponent.Net.RepeatedTimer(timeOutInterval, CheckTimeOut);
|
||||
}
|
||||
|
||||
private void CheckTimeOut()
|
||||
{
|
||||
if (TimeHelper.Now - LastTime < TimeOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Session entityReference = Session;
|
||||
|
||||
if (entityReference == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entityReference.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止心跳功能。
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (TimerId != 0)
|
||||
{
|
||||
TimerComponent?.Net.Remove(ref TimerId);
|
||||
}
|
||||
|
||||
if (TimeOutTimerId != 0)
|
||||
{
|
||||
TimerComponent?.Net.Remove(ref TimeOutTimerId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送心跳请求并处理响应。
|
||||
/// </summary>
|
||||
/// <returns>表示进行中操作的异步任务。</returns>
|
||||
private async FTask RepeatedSend()
|
||||
{
|
||||
if (SelfRunTimeId != RuntimeId)
|
||||
{
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Session session = Session;
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var requestTime = TimeHelper.Now;
|
||||
|
||||
var pingResponse = (PingResponse)await session.Call(_pingRequest);
|
||||
|
||||
if (pingResponse.ErrorCode != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var responseTime = TimeHelper.Now;
|
||||
LastTime = responseTime;
|
||||
Ping = (int)(responseTime - requestTime) / 2;
|
||||
TimeHelper.TimeDiff = pingResponse.Now + Ping - responseTime;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Timer;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network;
|
||||
|
||||
public class SessionIdleCheckerComponentAwakeSystem : AwakeSystem<SessionIdleCheckerComponent>
|
||||
{
|
||||
protected override void Awake(SessionIdleCheckerComponent self)
|
||||
{
|
||||
self.TimerComponent = self.Scene.TimerComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 负责检查会话空闲超时的组件。
|
||||
/// </summary>
|
||||
public class SessionIdleCheckerComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 空闲超时时间(毫秒)
|
||||
/// </summary>
|
||||
private long _timeOut;
|
||||
/// <summary>
|
||||
/// 检查计时器的 ID
|
||||
/// </summary>
|
||||
private long _timerId;
|
||||
/// <summary>
|
||||
/// 用于确保组件完整性的自身运行时 ID
|
||||
/// </summary>
|
||||
private long _selfRuntimeId;
|
||||
/// <summary>
|
||||
/// 对会话对象的引用
|
||||
/// </summary>
|
||||
private Session _session;
|
||||
public TimerComponent TimerComponent;
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Dispose 方法以释放资源。
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
Stop(); // 停止检查计时器
|
||||
_timeOut = 0; // 重置空闲超时时间
|
||||
_selfRuntimeId = 0; // 重置自身运行时 ID
|
||||
_session = null; // 清除会话引用
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的间隔和空闲超时时间启动空闲检查功能。
|
||||
/// </summary>
|
||||
/// <param name="interval">以毫秒为单位的检查间隔。</param>
|
||||
/// <param name="timeOut">以毫秒为单位的空闲超时时间。</param>
|
||||
public void Start(int interval, int timeOut)
|
||||
{
|
||||
_timeOut = timeOut;
|
||||
_session = (Session)Parent;
|
||||
_selfRuntimeId = RuntimeId;
|
||||
// 安排重复计时器,在指定的间隔内执行 Check 方法
|
||||
_timerId = TimerComponent.Net.RepeatedTimer(interval, Check);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止空闲检查功能。
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (_timerId == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TimerComponent.Net.Remove(ref _timerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行空闲检查操作。
|
||||
/// </summary>
|
||||
private void Check()
|
||||
{
|
||||
if (_selfRuntimeId != RuntimeId || IsDisposed || _session == null)
|
||||
{
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
var timeNow = TimeHelper.Now;
|
||||
|
||||
if (timeNow - _session.LastReceiveTime < _timeOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Warning($"session timeout id:{Id} timeNow:{timeNow} _session.LastReceiveTime:{_session.LastReceiveTime} _timeOut:{_timeOut}");
|
||||
_session.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,156 @@
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Timer;
|
||||
|
||||
#if FANTASY_UNITY
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
public class SessionHeartbeatComponentAwakeSystem : AwakeSystem<SessionHeartbeatComponent>
|
||||
{
|
||||
protected override void Awake(SessionHeartbeatComponent self)
|
||||
{
|
||||
self.TimerComponent = self.Scene.TimerComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 负责管理会话心跳的组件。
|
||||
/// </summary>
|
||||
public class SessionHeartbeatComponent : Entity
|
||||
{
|
||||
public int TimeOut;
|
||||
public long TimerId;
|
||||
public long LastTime;
|
||||
public long SelfRunTimeId;
|
||||
public long TimeOutTimerId;
|
||||
public TimerComponent TimerComponent;
|
||||
public EntityReference<Session> Session;
|
||||
private readonly PingRequest _pingRequest = new PingRequest();
|
||||
|
||||
public int Ping { get; private set; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
Ping = 0;
|
||||
Session = null;
|
||||
TimeOut = 0;
|
||||
LastTime = 0;
|
||||
SelfRunTimeId = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的间隔启动心跳功能。
|
||||
/// </summary>
|
||||
/// <param name="interval">以毫秒为单位的心跳请求发送间隔。</param>
|
||||
/// <param name="timeOut">设置与服务器的通信超时时间,如果超过这个时间限制,将自动断开会话(Session)。</param>
|
||||
/// <param name="timeOutInterval">用于检测与服务器连接超时频率。</param>
|
||||
public void Start(int interval, int timeOut = 5000, int timeOutInterval = 3000)
|
||||
{
|
||||
TimeOut = timeOut + interval;
|
||||
Session = (Session)Parent;
|
||||
SelfRunTimeId = RuntimeId;
|
||||
LastTime = TimeHelper.Now;
|
||||
|
||||
if (TimerComponent == null)
|
||||
{
|
||||
Log.Error("请在Unity的菜单执行Fantasy->Generate link.xml再重新打包");
|
||||
return;
|
||||
}
|
||||
|
||||
TimerId = TimerComponent.Unity.RepeatedTimer(interval, () =>
|
||||
{
|
||||
RepeatedSend().Coroutine();
|
||||
});
|
||||
TimeOutTimerId = TimerComponent.Unity.RepeatedTimer(timeOutInterval, CheckTimeOut);
|
||||
}
|
||||
|
||||
private void CheckTimeOut()
|
||||
{
|
||||
if (TimeHelper.Now - LastTime < TimeOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Session entityReference = Session;
|
||||
|
||||
if (entityReference == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entityReference.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止心跳功能。
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (TimerId != 0)
|
||||
{
|
||||
TimerComponent?.Unity.Remove(ref TimerId);
|
||||
}
|
||||
|
||||
if (TimeOutTimerId != 0)
|
||||
{
|
||||
TimerComponent?.Unity.Remove(ref TimeOutTimerId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送心跳请求并处理响应。
|
||||
/// </summary>
|
||||
/// <returns>表示进行中操作的异步任务。</returns>
|
||||
private async FTask RepeatedSend()
|
||||
{
|
||||
if (SelfRunTimeId != RuntimeId)
|
||||
{
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Session session = Session;
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var requestTime = TimeHelper.Now;
|
||||
var pingResponse = (PingResponse)await session.Call(_pingRequest);
|
||||
|
||||
if (pingResponse.ErrorCode != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var responseTime = TimeHelper.Now;
|
||||
LastTime = responseTime;
|
||||
Ping = (int)(responseTime - requestTime) / 2;
|
||||
TimeHelper.TimeDiff = pingResponse.Now + Ping - responseTime;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
#if FANTASY_NET
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
using Fantasy.IdFactory;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy.Scheduler;
|
||||
|
||||
internal static class ProcessScheduler
|
||||
{
|
||||
public static void Scheduler(this ProcessSession session, Type messageType, uint rpcId, long routeId, APackInfo packInfo)
|
||||
{
|
||||
switch (packInfo.OpCodeIdStruct.Protocol)
|
||||
{
|
||||
case OpCodeType.InnerResponse:
|
||||
case OpCodeType.InnerRouteResponse:
|
||||
case OpCodeType.InnerAddressableResponse:
|
||||
case OpCodeType.InnerRoamingResponse:
|
||||
case OpCodeType.OuterAddressableResponse:
|
||||
case OpCodeType.OuterCustomRouteResponse:
|
||||
case OpCodeType.OuterRoamingResponse:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var sessionScene = session.Scene;
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
sessionScene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
// 因为有可能是其他Scene线程下发送过来的、所以必须放到当前Scene进程下运行。
|
||||
sessionScene.NetworkMessagingComponent.ResponseHandler(rpcId, (IResponse)message);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerRouteMessage:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
throw new Exception($"not found scene routeId:{routeId}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine();
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerRouteRequest:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
throw new Exception($"not found scene routeId:{routeId}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
sceneMessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId);
|
||||
return;
|
||||
}
|
||||
|
||||
sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine();
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
case OpCodeType.OuterCustomRouteMessage:
|
||||
case OpCodeType.OuterAddressableRequest:
|
||||
case OpCodeType.OuterCustomRouteRequest:
|
||||
case OpCodeType.OuterRoamingMessage:
|
||||
case OpCodeType.OuterRoamingRequest:
|
||||
{
|
||||
using (packInfo)
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
throw new NotSupportedException($"not found scene routeId = {routeId}");
|
||||
}
|
||||
|
||||
var message = packInfo.Deserialize(messageType);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId);
|
||||
return;
|
||||
}
|
||||
|
||||
scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var packInfoProtocolCode = packInfo.ProtocolCode;
|
||||
packInfo.Dispose();
|
||||
throw new NotSupportedException($"SessionInnerScheduler Received unsupported message protocolCode:{packInfoProtocolCode} messageType:{messageType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Scheduler(this ProcessSession session, Type messageType, uint rpcId, long routeId, uint protocolCode, object message)
|
||||
{
|
||||
OpCodeIdStruct opCodeIdStruct = protocolCode;
|
||||
|
||||
switch (opCodeIdStruct.Protocol)
|
||||
{
|
||||
case OpCodeType.InnerResponse:
|
||||
case OpCodeType.InnerRouteResponse:
|
||||
case OpCodeType.InnerAddressableResponse:
|
||||
case OpCodeType.InnerRoamingResponse:
|
||||
case OpCodeType.OuterAddressableResponse:
|
||||
case OpCodeType.OuterCustomRouteResponse:
|
||||
case OpCodeType.OuterRoamingResponse:
|
||||
{
|
||||
var sessionScene = session.Scene;
|
||||
sessionScene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var iResponse = (IResponse)session.Deserialize(messageType, message, ref opCodeIdStruct);
|
||||
// 因为有可能是其他Scene线程下发送过来的、所以必须放到当前Scene进程下运行。
|
||||
sessionScene.NetworkMessagingComponent.ResponseHandler(rpcId, iResponse);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerRoamingMessage:
|
||||
case OpCodeType.InnerAddressableMessage:
|
||||
case OpCodeType.InnerRouteMessage:
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
throw new Exception($"not found scene routeId:{routeId}");
|
||||
}
|
||||
|
||||
var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.InnerAddressableRequest:
|
||||
case OpCodeType.InnerRoamingRequest:
|
||||
case OpCodeType.InnerRouteRequest:
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
throw new Exception($"not found scene routeId:{routeId}");
|
||||
}
|
||||
|
||||
var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent;
|
||||
|
||||
if (entity == null || entity.IsDisposed)
|
||||
{
|
||||
sceneMessageDispatcherComponent.FailRouteResponse(session, message.GetType(), InnerErrorCode.ErrNotFoundRoute, rpcId);
|
||||
return;
|
||||
}
|
||||
|
||||
sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
case OpCodeType.OuterAddressableMessage:
|
||||
case OpCodeType.OuterCustomRouteMessage:
|
||||
case OpCodeType.OuterRoamingMessage:
|
||||
{
|
||||
var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId);
|
||||
|
||||
if (!Process.TryGetScene(sceneId, out var scene))
|
||||
{
|
||||
Log.Error($"not found scene routeId:{routeId}");
|
||||
return;
|
||||
}
|
||||
|
||||
var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct);
|
||||
|
||||
scene.ThreadSynchronizationContext.Post(() =>
|
||||
{
|
||||
var entity = scene.GetEntity(routeId);
|
||||
|
||||
switch (entity)
|
||||
{
|
||||
case null:
|
||||
{
|
||||
// 执行到这里是说明Session已经断开了
|
||||
// 因为这里是其他服务器Send到外网的数据、所以不需要给发送端返回就可以
|
||||
return;
|
||||
}
|
||||
case Session gateSession:
|
||||
{
|
||||
// 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息
|
||||
gateSession.Send((IMessage)messageObject, rpcId);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException($"SessionInnerScheduler Received unsupported message protocolCode:{protocolCode} messageType:{messageType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,124 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Pool;
|
||||
using Fantasy.Scheduler;
|
||||
using Fantasy.Serialize;
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network;
|
||||
|
||||
/// <summary>
|
||||
/// 网络服务器内部会话。
|
||||
/// </summary>
|
||||
public sealed class ProcessSession : Session
|
||||
{
|
||||
private readonly MemoryStreamBufferPool _memoryStreamBufferPool = new MemoryStreamBufferPool();
|
||||
private readonly Dictionary<Type, Func<object>> _createInstances = new Dictionary<Type, Func<object>>();
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息到服务器内部。
|
||||
/// </summary>
|
||||
/// <param name="message">要发送的消息。</param>
|
||||
/// <param name="rpcId">RPC 标识符。</param>
|
||||
/// <param name="routeId">路由标识符。</param>
|
||||
public override void Send(IMessage message, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Scheduler(message.GetType(), rpcId, routeId, message.OpCode(), message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送路由消息到服务器内部。
|
||||
/// </summary>
|
||||
/// <param name="routeMessage">要发送的路由消息。</param>
|
||||
/// <param name="rpcId">RPC 标识符。</param>
|
||||
/// <param name="routeId">路由标识符。</param>
|
||||
public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Scheduler(routeMessage.GetType(), rpcId, routeId, routeMessage.OpCode(), routeMessage);
|
||||
}
|
||||
|
||||
public override void Send(uint rpcId, long routeId, Type messageType, APackInfo packInfo)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Scheduler(messageType, rpcId, routeId, packInfo);
|
||||
}
|
||||
|
||||
public override void Send(ProcessPackInfo packInfo, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
this.Scheduler(packInfo.MessageType, rpcId, routeId, packInfo);
|
||||
}
|
||||
|
||||
public override void Send(MemoryStreamBuffer memoryStream, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
throw new Exception("The use of this method is not supported");
|
||||
}
|
||||
|
||||
public override FTask<IResponse> Call(IRouteRequest request, long routeId = 0)
|
||||
{
|
||||
throw new Exception("The use of this method is not supported");
|
||||
}
|
||||
|
||||
public override FTask<IResponse> Call(IRequest request, long routeId = 0)
|
||||
{
|
||||
throw new Exception("The use of this method is not supported");
|
||||
}
|
||||
|
||||
public object Deserialize(Type messageType, object message, ref OpCodeIdStruct opCodeIdStruct)
|
||||
{
|
||||
var memoryStream = _memoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.None);
|
||||
|
||||
try
|
||||
{
|
||||
if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer))
|
||||
{
|
||||
serializer.Serialize(messageType, message, memoryStream);
|
||||
|
||||
if (memoryStream.Position == 0)
|
||||
{
|
||||
if (_createInstances.TryGetValue(messageType, out var createInstance))
|
||||
{
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
createInstance = CreateInstance.CreateObject(messageType);
|
||||
_createInstances.Add(messageType, createInstance);
|
||||
return createInstance();
|
||||
}
|
||||
|
||||
memoryStream.SetLength(memoryStream.Position);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
return serializer.Deserialize(messageType, memoryStream);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"ProcessSession.Deserialize {e}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_memoryStreamBufferPool.ReturnMemoryStream(memoryStream);
|
||||
}
|
||||
|
||||
throw new Exception($"type:{messageType} Does not support processing protocol");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network;
|
||||
|
||||
internal sealed class ProcessSessionInfo(Session session, AClientNetwork aClientNetwork) : IDisposable
|
||||
{
|
||||
public readonly Session Session = session;
|
||||
public readonly AClientNetwork AClientNetwork = aClientNetwork;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Session.Dispose();
|
||||
AClientNetwork?.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
272
Fantasy/Fantays.Console/Runtime/Core/Network/Session/Session.cs
Normal file
272
Fantasy/Fantays.Console/Runtime/Core/Network/Session/Session.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
// ReSharper disable RedundantUsingDirective
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.PacketParser;
|
||||
using Fantasy.PacketParser.Interface;
|
||||
using Fantasy.Scheduler;
|
||||
using Fantasy.Serialize;
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Network.Route;
|
||||
using Fantasy.Platform.Net;
|
||||
using Fantasy.Network.Roaming;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#endif
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
#pragma warning disable CS8603
|
||||
#pragma warning disable CS8601
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Fantasy.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络会话的基类,用于管理网络通信。
|
||||
/// </summary>
|
||||
public class Session : Entity, ISupportedMultiEntity
|
||||
{
|
||||
private uint _rpcId;
|
||||
internal long LastReceiveTime;
|
||||
/// <summary>
|
||||
/// 关联的网络连接通道
|
||||
/// </summary>
|
||||
internal INetworkChannel Channel { get; private set; }
|
||||
/// <summary>
|
||||
/// 当前Session的终结点信息
|
||||
/// </summary>
|
||||
public IPEndPoint RemoteEndPoint { get; private set; }
|
||||
private ANetworkMessageScheduler NetworkMessageScheduler { get; set;}
|
||||
internal readonly Dictionary<long, FTask<IResponse>> RequestCallback = new();
|
||||
/// <summary>
|
||||
/// Session的Dispose委托
|
||||
/// </summary>
|
||||
internal event Action OnDispose;
|
||||
#if FANTASY_NET
|
||||
internal RouteComponent RouteComponent;
|
||||
internal SessionRoamingComponent SessionRoamingComponent;
|
||||
internal AddressableRouteComponent AddressableRouteComponent;
|
||||
internal static Session Create(ANetworkMessageScheduler networkMessageScheduler, ANetworkServerChannel channel, NetworkTarget networkTarget)
|
||||
{
|
||||
var session = Entity.Create<Session>(channel.Scene, false, true);
|
||||
session.Channel = channel;
|
||||
session.NetworkMessageScheduler = networkMessageScheduler;
|
||||
session.RemoteEndPoint = channel.RemoteEndPoint as IPEndPoint;
|
||||
session.OnDispose = channel.Dispose;
|
||||
session.LastReceiveTime = TimeHelper.Now;
|
||||
// 在外部网络目标下,添加会话空闲检查组件
|
||||
if (networkTarget == NetworkTarget.Outer)
|
||||
{
|
||||
var interval = ProcessDefine.SessionIdleCheckerInterval;
|
||||
var timeOut = ProcessDefine.SessionIdleCheckerTimeout;
|
||||
session.AddComponent<SessionIdleCheckerComponent>().Start(interval, timeOut);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
#endif
|
||||
internal static Session Create(AClientNetwork network, IPEndPoint remoteEndPoint)
|
||||
{
|
||||
// 创建会话实例
|
||||
var session = Entity.Create<Session>(network.Scene, false, true);
|
||||
session.Channel = network;
|
||||
session.RemoteEndPoint = remoteEndPoint;
|
||||
session.OnDispose = network.Dispose;
|
||||
session.NetworkMessageScheduler = network.NetworkMessageScheduler;
|
||||
session.LastReceiveTime = TimeHelper.Now;
|
||||
return session;
|
||||
}
|
||||
#if FANTASY_NET
|
||||
internal static ProcessSession CreateInnerSession(Scene scene)
|
||||
{
|
||||
var session = Entity.Create<ProcessSession>(scene, false, false);
|
||||
session.NetworkMessageScheduler = new InnerMessageScheduler(scene);
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息,框架内部使用建议不要用这个方法。
|
||||
/// </summary>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个RPCId</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
/// <param name="messageType">消息的类型</param>
|
||||
/// <param name="packInfo">packInfo消息包</param>
|
||||
public virtual void Send(uint rpcId, long routeId, Type messageType, APackInfo packInfo)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Channel.Send(rpcId, routeId, packInfo.MemoryStream, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息,框架内部使用建议不要用这个方法。
|
||||
/// </summary>
|
||||
/// <param name="packInfo">一个ProcessPackInfo消息包</param>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个RPCId</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
public virtual void Send(ProcessPackInfo packInfo, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (packInfo)
|
||||
{
|
||||
Channel.Send(rpcId, routeId, packInfo.MemoryStream, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息
|
||||
/// </summary>
|
||||
/// <param name="memoryStream">需要发送的MemoryStreamBuffer</param>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个RPCId</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
public virtual void Send(MemoryStreamBuffer memoryStream, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Channel.Send(rpcId, routeId, memoryStream, null);
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 销毁一个Session,当执行了这个方法会自动断开网络的连接。
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rpcId = 0;
|
||||
LastReceiveTime = 0;
|
||||
Channel = null;
|
||||
RemoteEndPoint = null;
|
||||
NetworkMessageScheduler = null;
|
||||
#if FANTASY_NET
|
||||
SessionRoamingComponent = null;
|
||||
RouteComponent = null;
|
||||
AddressableRouteComponent = null;
|
||||
#endif
|
||||
base.Dispose();
|
||||
|
||||
// 终止所有等待中的请求回调
|
||||
foreach (var requestCallback in RequestCallback.Values.ToArray())
|
||||
{
|
||||
requestCallback.SetException(new Exception($"session is dispose: {Id}"));
|
||||
}
|
||||
|
||||
RequestCallback.Clear();
|
||||
OnDispose?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息的实例</param>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个RPCId</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
public virtual void Send(IMessage message, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Channel.Send(rpcId, routeId, null, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个消息
|
||||
/// </summary>
|
||||
/// <param name="routeMessage">消息的实例,不同的是这个是发送Route消息使用的</param>
|
||||
/// <param name="rpcId">如果是RPC消息需要传递一个RPCId</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
public virtual void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Channel.Send(rpcId, routeId, null, routeMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个RPC消息
|
||||
/// </summary>
|
||||
/// <param name="request">请求Route消息的实例</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
/// <returns></returns>
|
||||
public virtual FTask<IResponse> Call(IRouteRequest request, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var requestCallback = FTask<IResponse>.Create();
|
||||
var rpcId = ++_rpcId;
|
||||
RequestCallback.Add(rpcId, requestCallback);
|
||||
Send(request, rpcId, routeId);
|
||||
return requestCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个RPC消息
|
||||
/// </summary>
|
||||
/// <param name="request">请求消息的实例</param>
|
||||
/// <param name="routeId">routeId</param>
|
||||
/// <returns></returns>
|
||||
public virtual FTask<IResponse> Call(IRequest request, long routeId = 0)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var requestCallback = FTask<IResponse>.Create();
|
||||
var rpcId = ++_rpcId;
|
||||
RequestCallback.Add(rpcId, requestCallback);
|
||||
Send(request, rpcId, routeId);
|
||||
return requestCallback;
|
||||
}
|
||||
|
||||
internal void Receive(APackInfo packInfo)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LastReceiveTime = TimeHelper.Now;
|
||||
|
||||
try
|
||||
{
|
||||
NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 如果解析失败,只有一种可能,那就是有人恶意发包。
|
||||
// 所以这里强制关闭了当前连接。不让对方一直发包。
|
||||
Dispose();
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user