XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.ServerInteractive

[DLL] Server interaction extension, including user identity verification and querying in conjunction with AutoConfig

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEExtension.NetCore.ServerInteractive

支持多核心同端口绑定及主入口点分发

实现 XFEServerCoreHost,支持同端口多核心服务器通过不同 MainEntryPoint 隔离与分发 HTTP/WebSocket 事件。XFEServerCore 启动与请求处理逻辑调整,支持入口点规范化、路由提取和优先级判定。XFEServerCoreBuilder 构建时自动规范化入口点。XFEServerCoreProcessService 启动流程优化,异常重启逻辑增强。CyberCommServer 事件绑定调整为多核心共享。补充多核心同端口用法及路由分发规则文档。版本号升级至 3.3.0,优化调试输出和异常信息,兼容旧用法。

99b68ae
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

12 个文件 +435 -94
Modified README.md +26 -0
@@ -100,6 +100,32 @@ var server = XFEServerBuilder.CreateBuilder()
100 100 await server.Start(); // Start all initializers, sync/async services, and core services
101 101 ```
102 102
103 Multiple server cores can bind to the same port as long as their `MainEntryPoint` values differ. The framework starts one underlying listener for the shared port and dispatches HTTP and WebSocket events by the combination of port and main entry point:
104
105 ```csharp
106 var apiCore = XFEServerCoreBuilder.CreateBuilder().Build(options =>
107 {
108 options.ServerCoreName = "ApiServer";
109 options.MainEntryPoint = "api";
110 options.BindIP("http://localhost:3300/");
111 });
112
113 var backendCore = XFEServerCoreBuilder.CreateBuilder().Build(options =>
114 {
115 options.ServerCoreName = "BackendServer";
116 options.MainEntryPoint = "backend";
117 options.BindIP("http://localhost:3300/"); // Shares the port with apiCore
118 });
119
120 var server = XFEServerBuilder.CreateBuilder()
121 .UseXFEServer()
122 .AddServerCore(apiCore) // /api/{sub-route}
123 .AddServerCore(backendCore) // /backend/{sub-route}
124 .Build();
125 ```
126
127 `MainEntryPoint` must be unique on each port; otherwise, server startup throws `XFEServerBuilderException`. Leading, trailing, and duplicate `/` separators are normalized automatically. When nested entry points both match, the longer, more specific entry point wins; an empty entry point acts as the fallback core for that port.
128
103 129 `UseXFEServer()` is equivalent to:
104 130
105 131 ```csharp
Modified README.zh-CN.md +27 -1
@@ -100,6 +100,32 @@ var server = XFEServerBuilder.CreateBuilder()
100 100 await server.Start(); // 启动所有初始化服务、同步/异步服务及核心服务
101 101 ```
102 102
103 多个核心服务器可以绑定同一个端口,只要它们的 `MainEntryPoint` 不同。框架会为共享端口只启动一个底层监听器,并按“端口 + 主入口点”把 HTTP 和 WebSocket 事件分发给对应核心:
104
105 ```csharp
106 var apiCore = XFEServerCoreBuilder.CreateBuilder().Build(options =>
107 {
108 options.ServerCoreName = "ApiServer";
109 options.MainEntryPoint = "api";
110 options.BindIP("http://localhost:3300/");
111 });
112
113 var backendCore = XFEServerCoreBuilder.CreateBuilder().Build(options =>
114 {
115 options.ServerCoreName = "BackendServer";
116 options.MainEntryPoint = "backend";
117 options.BindIP("http://localhost:3300/"); // 与 apiCore 共享端口
118 });
119
120 var server = XFEServerBuilder.CreateBuilder()
121 .UseXFEServer()
122 .AddServerCore(apiCore) // /api/{次级路由}
123 .AddServerCore(backendCore) // /backend/{次级路由}
124 .Build();
125 ```
126
127 同一端口上的 `MainEntryPoint` 必须唯一,否则服务器启动时会抛出 `XFEServerBuilderException`。主入口点会自动移除首尾及重复的 `/`;嵌套入口点同时匹配时优先选择更长、更具体的入口点,空入口点则作为该端口的后备核心。
128
103 129 `UseXFEServer()` 等价于:
104 130
105 131 ```csharp
@@ -813,4 +839,4 @@ public partial class UserRequestService<T> : StandardRequestServiceBase where T
813 839
814 840 **Q:客户端请求总是返回 500**
815 841 - 检查 `options.RequestAddress` 末尾是否有多余的斜杠(框架会自动拼接路由:`RequestAddress + "/" + route`)
816 - 确认服务端路由与请求名称完全一致(区分大小写)
842 - 确认服务端路由与请求名称完全一致(区分大小写)
Modified XFEExtension.NetCore.ServerInteractive.TServer/Program.cs +3 -3
@@ -62,9 +62,9 @@ var server = XFEServerBuilder.CreateBuilder() // 创建服务器构建器
62 62 options.AcceptPost = true;
63 63 options.AcceptNonStandardJson = true;
64 64 options.MainEntryPoint = "backend";
65 options.BindIP("http://localhost:3302/")
66 .BindIP("https://localhost:3402/");
65 options.BindIP("http://localhost:3301/")
66 .BindIP("https://localhost:3401/");
67 67 })
68 68 )
69 69 .Build(); // 构建服务器
70 await server.Start(); // 启动服务
70 await server.Start(); // 启动服务
Modified XFEExtension.NetCore.ServerInteractive.Test/Program.cs +2 -2
@@ -48,7 +48,7 @@ internal class Program
48 48 }, response => response)
49 49 .Build(options =>
50 50 {
51 options.RequestAddress = "http://localhost:3302/backend";
51 options.RequestAddress = "http://localhost:3301/backend";
52 52 });
53 53
54 54 private static readonly TableRequester TableRequester = new();
@@ -535,4 +535,4 @@ internal class Program
535 535 }
536 536
537 537 #endregion
538 }
538 }
Modified XFEExtension.NetCore.ServerInteractive/Options/XFEServerCoreOptions.cs +2 -1
@@ -36,7 +36,8 @@ public class XFEServerCoreOptions
36 36 /// </summary>
37 37 public Func<CyberCommRequestEventArgs, string> GetIPFunction { get; set; } = args => args.ClientIP;
38 38 /// <summary>
39 /// 主入口点路径(ServerCore主要入口点),默认为空字符串(为空时直接使用次级入口点)
39 /// 主入口点路径(ServerCore主要入口点),默认为空字符串(为空时直接使用次级入口点)。
40 /// 多个核心服务器绑定同一端口时,使用不同的主入口点区分核心服务器。
40 41 /// </summary>
41 42 public string MainEntryPoint { get; set; } = string.Empty;
42 43
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Server/Services/ServerService/XFEServerCoreProcessService.cs +33 -45
@@ -1,4 +1,5 @@
1 1 using XFEExtension.NetCore.ServerInteractive.Implements.ServerService;
2 using XFEExtension.NetCore.ServerInteractive.Utilities.Server;
2 3
3 4 namespace XFEExtension.NetCore.ServerInteractive.Utilities.Server.Services.ServerService;
4 5
@@ -10,61 +11,48 @@ public class XFEServerCoreProcessService : ServerCoreProcessServiceBase
10 11 /// <inheritdoc/>
11 12 /// <exception cref="ArgumentNullException"></exception>
12 13 public override async Task ProcessServerCore()
14 {
15 Console.WriteLine(File.Exists("server.log") ? "[DEBUG]找到日志文件!" : "[DEBUG]未找到日志文件!");
16
17 var xfeServerCores = ServerCoreServiceList.OfType<XFEServerCore>().ToList();
18 var serverCoreHosts = XFEServerCoreHost.Configure(xfeServerCores);
19 var taskList = serverCoreHosts
20 .Select(host => KeepServerRunning(host.ServerCoreNames, host.BindingIPAddressList, host.StartServerCore))
21 .Concat(ServerCoreServiceList
22 .Where(serverCoreService => serverCoreService is not XFEServerCore)
23 .Select(serverCoreService => KeepServerRunning(
24 serverCoreService.ServerCoreName,
25 serverCoreService.BindingIPAddressList,
26 serverCoreService.StartServerCore)))
27 .ToList();
28
29 await Task.WhenAll(taskList);
30 }
31
32 private static async Task KeepServerRunning(string serverName, IReadOnlyList<string> bindingIPAddressList, Func<Task> startServer)
13 33 {
14 34 while (true)
15 35 {
16 36 try
17 37 {
18 Console.WriteLine(File.Exists("server.log") ? "[DEBUG]找到日志文件!" : "[DEBUG]未找到日志文件!");
19 var taskList = ServerCoreServiceList.Select(serverCoreService => Task.Run(async () =>
20 {
21 while (true)
22 {
23 try
24 {
25 var i = 0;
26 Console.WriteLine($"[DEBUG]正在启动服务器:{serverCoreService.ServerCoreName}...");
27 serverCoreService.BindingIPAddressList.ForEach(ip => Console.WriteLine($"[DEBUG]{serverCoreService.ServerCoreName}绑定IP-{++i}:{ip}"));
28 await serverCoreService.StartServerCore();
29 Console.WriteLine($"[ERROR]服务器({serverCoreService.ServerCoreName})因未知原因自行结束!");
30 Console.WriteLine($"[DEBUG]准备重启服务器({serverCoreService.ServerCoreName})");
31 await Task.Delay(1000);
32 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 3...");
33 await Task.Delay(1000);
34 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 2...");
35 await Task.Delay(1000);
36 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 1...");
37 }
38 catch (Exception ex)
39 {
40 Console.WriteLine($"[ERROR]服务器({serverCoreService.ServerCoreName})错误:{ex.Message}:{ex.InnerException?.Message}");
41 Console.WriteLine($"[TRACE]{ex.StackTrace}");
42 Console.WriteLine($"[DEBUG]准备重启服务器({serverCoreService.ServerCoreName})");
43 await Task.Delay(1000);
44 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 3...");
45 await Task.Delay(1000);
46 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 2...");
47 await Task.Delay(1000);
48 Console.WriteLine($"[DEBUG]正在重启服务器({serverCoreService.ServerCoreName}) 1...");
49 }
50 }
51 }))
52 .ToList();
53 await Task.WhenAll(taskList);
38 Console.WriteLine($"[DEBUG]正在启动服务器:{serverName}...");
39 for (var i = 0; i < bindingIPAddressList.Count; i++)
40 Console.WriteLine($"[DEBUG]{serverName}绑定IP-{i + 1}:{bindingIPAddressList[i]}");
41 await startServer();
42 Console.WriteLine($"[ERROR]服务器({serverName})因未知原因自行结束!");
54 43 }
55 44 catch (Exception ex)
56 45 {
57 Console.WriteLine($"[DEBUG]服务器错误:{ex}");
58 Console.WriteLine($"[ERROR]服务器错误:{ex.Message}");
46 Console.WriteLine($"[ERROR]服务器({serverName})错误:{ex.Message}:{ex.InnerException?.Message}");
59 47 Console.WriteLine($"[TRACE]{ex.StackTrace}");
60 Console.WriteLine("[DEBUG]准备重启服务器");
61 await Task.Delay(1000);
62 Console.WriteLine("[DEBUG]正在重启服务器 3...");
63 await Task.Delay(1000);
64 Console.WriteLine("[DEBUG]正在重启服务器 2...");
48 }
49
50 Console.WriteLine($"[DEBUG]准备重启服务器({serverName})");
51 for (var countDown = 3; countDown > 0; countDown--)
52 {
65 53 await Task.Delay(1000);
66 Console.WriteLine("[DEBUG]正在重启服务器 1...");
54 Console.WriteLine($"[DEBUG]正在重启服务器({serverName}) {countDown}...");
67 55 }
68 56 }
69 57 }
70 }
58 }
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServer.cs +2 -1
@@ -30,6 +30,7 @@ public class XFEServer
30 30 serverService.StartService();
31 31 foreach (var asyncServerService in AsyncServerServiceList)
32 32 await asyncServerService.StartServiceAsync();
33 XFEServerCoreHost.Configure(ServerCoreProcessService.ServerCoreServiceList.OfType<XFEServerCore>());
33 34 await ServerCoreProcessService.ProcessServerCore();
34 35 }
35 }
36 }
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerBuilder.cs +1 -0
@@ -134,6 +134,7 @@ public abstract class XFEServerBuilder : XFEBuilderBase<XFEServerBuilder>
134 134 if (!_addedProcessServiced || _serverCoreProcess is null)
135 135 throw new XFEServerBuilderException("未添加核心处理器!");
136 136 _serverCoreProcess.ServerCoreServiceList = _serverCoreServiceList;
137 _serverCoreProcess.XFEServer = XFEServer;
137 138 XFEServer.ServerCoreProcessService = _serverCoreProcess;
138 139 return XFEServer;
139 140 }
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCore.cs +100 -29
@@ -44,7 +44,8 @@ public abstract class XFEServerCore : ServerCoreServiceBase
44 44 /// </summary>
45 45 public Func<CyberCommRequestEventArgs, string> GetIPFunction { get; set; } = args => args.ClientIP;
46 46 /// <summary>
47 /// 主入口点路径(ServerCore主要入口点),默认为空字符串(为空时直接使用次级入口点)
47 /// 主入口点路径(ServerCore主要入口点),默认为空字符串(为空时直接使用次级入口点)。
48 /// 多个核心服务器绑定同一端口时,使用不同的主入口点区分核心服务器。
48 49 /// </summary>
49 50 public string MainEntryPoint { get; set; } = string.Empty;
50 51 /// <summary>
@@ -68,7 +69,20 @@ public abstract class XFEServerCore : ServerCoreServiceBase
68 69 /// </summary>
69 70 public CyberCommServer CyberCommServer { get; internal set; } = new();
70 71
71 private async void CyberCommServer_RequestReceived(object? sender, CyberCommRequestEventArgs e)
72 /// <summary>
73 /// 当前核心所属的共享监听主机。
74 /// </summary>
75 internal XFEServerCoreHost? ServerCoreHost { get; set; }
76
77 /// <summary>
78 /// 主入口点包含的路径段数量。
79 /// </summary>
80 internal int MainEntryPointSegmentCount => GetEntryPointSegments(MainEntryPoint).Count;
81
82 /// <summary>
83 /// 处理已经由共享监听主机分发到当前核心的请求。
84 /// </summary>
85 internal async Task ProcessRequest(CyberCommRequestEventArgs e)
72 86 {
73 87 var r = new ServerCoreReturnArgs
74 88 {
@@ -129,7 +143,7 @@ public abstract class XFEServerCore : ServerCoreServiceBase
129 143 try
130 144 {
131 145 // 从URL中提取路由:www.xxx.com/[mainEntryPoint?][/subEntryPoint?]*
132 var url = e.Request.Url;
146 var url = e.Request.Url ?? e.RequestUrl;
133 147 if (url is null)
134 148 {
135 149 ServerCoreError?.Invoke(this, new()
@@ -140,30 +154,15 @@ public abstract class XFEServerCore : ServerCoreServiceBase
140 154 });
141 155 return;
142 156 }
143
144 var segments = url.Segments.Skip(1).Select(s => s.TrimEnd('/')).Where(s => !string.IsNullOrEmpty(s)).ToArray();
145
146 // 如果MainEntryPoint不为空,则需要匹配主入口点
147 string route;
148 if (!string.IsNullOrEmpty(MainEntryPoint))
157 if (!TryGetRoute(url, out var route))
149 158 {
150 if (segments.Length == 0 || segments[0] != MainEntryPoint)
159 ServerCoreError?.Invoke(this, new()
151 160 {
152 ServerCoreError?.Invoke(this, new()
153 {
154 StatusCode = HttpStatusCode.BadRequest,
155 ReturnArgs = r,
156 ServerException = new ProcessStandardRequestException($"请求路径不匹配主入口点: {MainEntryPoint}")
157 });
158 return;
159 }
160 // 跳过主入口点,获取次级路由
161 route = string.Join("/", segments.Skip(1));
162 }
163 else
164 {
165 // 直接使用所有segments作为路由
166 route = string.Join("/", segments);
161 StatusCode = HttpStatusCode.BadRequest,
162 ReturnArgs = r,
163 ServerException = new ProcessStandardRequestException($"请求路径不匹配主入口点: {MainEntryPoint}")
164 });
165 return;
167 166 }
168 167
169 168 if (route.IsNullOrEmpty())
@@ -282,8 +281,80 @@ public abstract class XFEServerCore : ServerCoreServiceBase
282 281 /// <returns></returns>
283 282 public override async Task StartServerCore()
284 283 {
285 CyberCommServer.ServerUrlArray = [.. BindingIPAddressList];
286 CyberCommServer.RequestReceived += CyberCommServer_RequestReceived;
287 await CyberCommServer.StartCyberCommServer();
284 ServerCoreHost ??= XFEServerCoreHost.Configure([this]).Single();
285 await ServerCoreHost.StartServerCore();
288 286 }
289 }
287
288 /// <summary>
289 /// 规范化主入口点,移除首尾及重复的路径分隔符。
290 /// </summary>
291 internal static string NormalizeMainEntryPoint(string? mainEntryPoint) => string.Join(
292 "/",
293 (mainEntryPoint ?? string.Empty).Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
294
295 /// <summary>
296 /// 获取请求URL中的全部非空路径段。
297 /// </summary>
298 internal static IReadOnlyList<string> GetRequestSegments(Uri requestUrl) => requestUrl.Segments
299 .Skip(1)
300 .Select(segment => Uri.UnescapeDataString(segment.Trim('/')))
301 .Where(segment => segment.Length > 0)
302 .ToArray();
303
304 /// <summary>
305 /// 尝试匹配当前核心的主入口点,并提取次级路由。
306 /// </summary>
307 internal bool TryGetRoute(Uri requestUrl, out string route)
308 {
309 var requestSegments = GetRequestSegments(requestUrl);
310 var entryPointSegments = GetEntryPointSegments(MainEntryPoint);
311 if (requestSegments.Count < entryPointSegments.Count)
312 {
313 route = string.Empty;
314 return false;
315 }
316
317 for (var i = 0; i < entryPointSegments.Count; i++)
318 {
319 if (requestSegments[i] == entryPointSegments[i]) continue;
320 route = string.Empty;
321 return false;
322 }
323
324 route = string.Join("/", requestSegments.Skip(entryPointSegments.Count));
325 return true;
326 }
327
328 internal void NotifyServerStarted(object? sender, EventArgs e)
329 {
330 foreach (var service in ServerCoreServiceList)
331 service.ServerStarted(sender, e);
332 }
333
334 internal void NotifyRequestReceived(object? sender, CyberCommRequestEventArgs e)
335 {
336 foreach (var service in ServerCoreServiceList)
337 service.RequestReceived(sender, e);
338 }
339
340 internal void NotifyClientConnected(object? sender, CyberCommServerEventArgs e)
341 {
342 foreach (var service in ServerCoreServiceList)
343 service.ClientConnected(sender, e);
344 }
345
346 internal void NotifyMessageReceived(object? sender, CyberCommServerEventArgs e)
347 {
348 foreach (var service in ServerCoreServiceList)
349 service.MessageReceived(sender, e);
350 }
351
352 internal void NotifyConnectionClosed(object? sender, CyberCommServerEventArgs e)
353 {
354 foreach (var service in ServerCoreServiceList)
355 service.ConnectionClosed(sender, e);
356 }
357
358 private static IReadOnlyList<string> GetEntryPointSegments(string mainEntryPoint) => mainEntryPoint
359 .Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
360 }
Modified XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCoreBuilder.cs +1 -6
@@ -148,11 +148,6 @@ public abstract class XFEServerCoreBuilder : XFEBuilderBase<XFEServerCoreBuilder
148 148 foreach (var serverCoreService in _serverCoreServiceList)
149 149 {
150 150 _xFEServerCore.ServerCoreServiceList.Add(serverCoreService);
151 _xFEServerCore.CyberCommServer.ServerStarted += serverCoreService.ServerStarted;
152 _xFEServerCore.CyberCommServer.RequestReceived += serverCoreService.RequestReceived;
153 _xFEServerCore.CyberCommServer.ClientConnected += serverCoreService.ClientConnected;
154 _xFEServerCore.CyberCommServer.MessageReceived += serverCoreService.MessageReceived;
155 _xFEServerCore.CyberCommServer.ConnectionClosed += serverCoreService.ConnectionClosed;
156 151 }
157 152 _xFEServerCore.ServerCoreVerifyServiceList = _serverCoreVerifyServiceList;
158 153 _xFEServerCore.StandardCoreServiceDictionary = _serverStandardCoreServiceDictionary;
@@ -166,7 +161,7 @@ public abstract class XFEServerCoreBuilder : XFEBuilderBase<XFEServerCoreBuilder
166 161 _xFEServerCore.AcceptGet = xFEServerCoreOptions.AcceptGet;
167 162 _xFEServerCore.AcceptPost = xFEServerCoreOptions.AcceptPost;
168 163 _xFEServerCore.GetIPFunction = xFEServerCoreOptions.GetIPFunction;
169 _xFEServerCore.MainEntryPoint = xFEServerCoreOptions.MainEntryPoint;
164 _xFEServerCore.MainEntryPoint = XFEServerCore.NormalizeMainEntryPoint(xFEServerCoreOptions.MainEntryPoint);
170 165 if (xFEServerCoreOptions.BindingIPAddress.Count > 0)
171 166 _xFEServerCore.BindingIPAddressList = xFEServerCoreOptions.BindingIPAddress;
172 167 }
Added XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCoreHost.cs +234 -0
@@ -0,0 +1,234 @@
1 using System.Net;
2 using XFEExtension.NetCore.CyberComm;
3 using XFEExtension.NetCore.ServerInteractive.Exceptions;
4
5 namespace XFEExtension.NetCore.ServerInteractive.Utilities.Server;
6
7 /// <summary>
8 /// 承载一个或多个共享监听端口的XFE服务器核心,并根据端口和主入口点分发请求。
9 /// </summary>
10 internal sealed class XFEServerCoreHost
11 {
12 private readonly IReadOnlyList<XFEServerCore> _serverCores;
13 private readonly Dictionary<XFEServerCore, HashSet<int>> _bindingPorts;
14 private readonly object _startLock = new();
15 private Task? _runningTask;
16
17 /// <summary>
18 /// 此主机包含的核心服务器名称。
19 /// </summary>
20 public string ServerCoreNames => string.Join(", ", _serverCores.Select(core => core.ServerCoreName));
21
22 /// <summary>
23 /// 此主机监听的全部地址。
24 /// </summary>
25 public IReadOnlyList<string> BindingIPAddressList { get; }
26
27 /// <summary>
28 /// 此主机使用的底层通讯服务器。
29 /// </summary>
30 public CyberCommServer CyberCommServer { get; }
31
32 private XFEServerCoreHost(IReadOnlyList<XFEServerCore> serverCores)
33 {
34 if (serverCores.Count == 0)
35 throw new ArgumentException("核心服务器主机至少需要包含一个核心服务器", nameof(serverCores));
36
37 _serverCores = serverCores;
38 foreach (var serverCore in serverCores)
39 serverCore.MainEntryPoint = XFEServerCore.NormalizeMainEntryPoint(serverCore.MainEntryPoint);
40 _bindingPorts = serverCores.ToDictionary(
41 core => core,
42 core => core.BindingIPAddressList.Select(GetBindingPort).ToHashSet());
43
44 ValidateServerCoreIdentities();
45
46 BindingIPAddressList = serverCores
47 .SelectMany(core => core.BindingIPAddressList)
48 .Distinct(StringComparer.OrdinalIgnoreCase)
49 .ToList();
50
51 CyberCommServer = serverCores[0].CyberCommServer;
52 CyberCommServer.ServerUrlArray = [.. BindingIPAddressList];
53 CyberCommServer.ServerStarted += CyberCommServer_ServerStarted;
54 CyberCommServer.RequestReceived += CyberCommServer_RequestReceived;
55 CyberCommServer.ClientConnected += CyberCommServer_ClientConnected;
56 CyberCommServer.MessageReceived += CyberCommServer_MessageReceived;
57 CyberCommServer.ConnectionClosed += CyberCommServer_ConnectionClosed;
58
59 foreach (var serverCore in serverCores)
60 {
61 serverCore.CyberCommServer = CyberCommServer;
62 serverCore.ServerCoreHost = this;
63 }
64 }
65
66 /// <summary>
67 /// 按共享端口将核心服务器划分为监听主机。
68 /// </summary>
69 public static IReadOnlyList<XFEServerCoreHost> Configure(IEnumerable<XFEServerCore> serverCores)
70 {
71 var coreList = serverCores.Distinct().ToList();
72 if (coreList.Count == 0)
73 return [];
74
75 if (coreList.All(core => core.ServerCoreHost is not null))
76 return coreList.Select(core => core.ServerCoreHost!).Distinct().ToList();
77 if (coreList.Any(core => core.ServerCoreHost is not null))
78 throw new InvalidOperationException("不能将已经分配监听主机的核心服务器与未分配的核心服务器混合配置");
79
80 var portMap = coreList.ToDictionary(
81 core => core,
82 core => core.BindingIPAddressList.Select(GetBindingPort).ToHashSet());
83 var remaining = new List<XFEServerCore>(coreList);
84 var hosts = new List<XFEServerCoreHost>();
85
86 while (remaining.Count > 0)
87 {
88 var first = remaining[0];
89 remaining.RemoveAt(0);
90
91 var group = new List<XFEServerCore> { first };
92 var groupPorts = new HashSet<int>(portMap[first]);
93 var addedCore = true;
94
95 // 端口集合可能通过多个核心间接相连,因此需要持续扩展到稳定状态。
96 while (addedCore)
97 {
98 addedCore = false;
99 foreach (var candidate in remaining.ToList())
100 {
101 if (!groupPorts.Overlaps(portMap[candidate])) continue;
102 remaining.Remove(candidate);
103 group.Add(candidate);
104 groupPorts.UnionWith(portMap[candidate]);
105 addedCore = true;
106 }
107 }
108
109 hosts.Add(new XFEServerCoreHost(group));
110 }
111
112 return hosts;
113 }
114
115 /// <summary>
116 /// 启动此主机的共享监听器。
117 /// </summary>
118 public Task StartServerCore()
119 {
120 lock (_startLock)
121 {
122 if (_runningTask is null || _runningTask.IsCompleted)
123 _runningTask = CyberCommServer.StartCyberCommServer();
124 return _runningTask;
125 }
126 }
127
128 private void ValidateServerCoreIdentities()
129 {
130 var identities = new Dictionary<(int Port, string MainEntryPoint), XFEServerCore>();
131 foreach (var serverCore in _serverCores)
132 {
133 foreach (var port in _bindingPorts[serverCore])
134 {
135 var identity = (port, serverCore.MainEntryPoint);
136 if (identities.TryGetValue(identity, out var registeredCore))
137 {
138 var entryPoint = serverCore.MainEntryPoint.Length == 0 ? "<空>" : serverCore.MainEntryPoint;
139 throw new XFEServerBuilderException(
140 $"端口 {port} 上的主入口点 '{entryPoint}' 已被核心服务器 '{registeredCore.ServerCoreName}' 使用,无法再注册核心服务器 '{serverCore.ServerCoreName}'");
141 }
142 identities.Add(identity, serverCore);
143 }
144 }
145 }
146
147 private XFEServerCore? FindServerCore(Uri requestUrl)
148 {
149 var requestPort = requestUrl.Port;
150 return _serverCores
151 .Where(core => _bindingPorts[core].Contains(requestPort))
152 .Select(core => (Core: core, Matches: core.TryGetRoute(requestUrl, out _)))
153 .Where(item => item.Matches)
154 // 嵌套主入口点同时匹配时,优先使用更具体的入口点。
155 .OrderByDescending(item => item.Core.MainEntryPointSegmentCount)
156 .Select(item => item.Core)
157 .FirstOrDefault();
158 }
159
160 private void CyberCommServer_ServerStarted(object? sender, EventArgs e)
161 {
162 foreach (var serverCore in _serverCores)
163 serverCore.NotifyServerStarted(sender, e);
164 }
165
166 private async void CyberCommServer_RequestReceived(object? sender, CyberCommRequestEventArgs e)
167 {
168 try
169 {
170 var requestUrl = e.Request.Url ?? e.RequestUrl;
171 if (requestUrl is null)
172 {
173 await e.ReplyAndClose("请求URL为空", HttpStatusCode.BadRequest);
174 return;
175 }
176 var serverCore = FindServerCore(requestUrl);
177 if (serverCore is null)
178 {
179 var entryPoint = XFEServerCore.GetRequestSegments(requestUrl).FirstOrDefault() ?? "<空>";
180 await e.ReplyAndClose($"未找到与端口 {requestUrl.Port} 和主入口点 '{entryPoint}' 匹配的核心服务器", HttpStatusCode.NotFound);
181 return;
182 }
183
184 serverCore.NotifyRequestReceived(sender, e);
185 await serverCore.ProcessRequest(e);
186 }
187 catch (Exception ex)
188 {
189 Console.WriteLine($"[ERROR]分发核心服务器请求时发生异常:{ex.Message}");
190 Console.WriteLine($"[TRACE]{ex.StackTrace}");
191 try
192 {
193 await e.ReplyAndClose("服务器内部异常", HttpStatusCode.InternalServerError);
194 }
195 catch
196 {
197 // 响应可能已经由核心服务器关闭,无需再次处理。
198 }
199 }
200 }
201
202 private void CyberCommServer_ClientConnected(object? sender, CyberCommServerEventArgs e)
203 {
204 if (e.RequestUrl is not null)
205 FindServerCore(e.RequestUrl)?.NotifyClientConnected(sender, e);
206 }
207
208 private void CyberCommServer_MessageReceived(object? sender, CyberCommServerEventArgs e)
209 {
210 if (e.RequestUrl is not null)
211 FindServerCore(e.RequestUrl)?.NotifyMessageReceived(sender, e);
212 }
213
214 private void CyberCommServer_ConnectionClosed(object? sender, CyberCommServerEventArgs e)
215 {
216 if (e.RequestUrl is not null)
217 FindServerCore(e.RequestUrl)?.NotifyConnectionClosed(sender, e);
218 }
219
220 private static int GetBindingPort(string bindingIPAddress)
221 {
222 ArgumentException.ThrowIfNullOrWhiteSpace(bindingIPAddress);
223
224 // System.Uri 不接受 HttpListener 的 '+'/'*' 主机通配符,替换后仅用于提取端口。
225 var parseableAddress = bindingIPAddress
226 .Replace("://+", "://localhost", StringComparison.OrdinalIgnoreCase)
227 .Replace("://*", "://localhost", StringComparison.OrdinalIgnoreCase);
228 if (!Uri.TryCreate(parseableAddress, UriKind.Absolute, out var bindingUri) ||
229 bindingUri.Scheme is not ("http" or "https"))
230 throw new XFEServerBuilderException($"无效的核心服务器绑定地址:'{bindingIPAddress}'");
231
232 return bindingUri.Port;
233 }
234 }
Modified XFEExtension.NetCore.ServerInteractive/XFEExtension.NetCore.ServerInteractive.csproj +4 -6
@@ -5,7 +5,7 @@
5 5 <ImplicitUsings>enable</ImplicitUsings>
6 6 <Nullable>enable</Nullable>
7 7 <GenerateDocumentationFile>True</GenerateDocumentationFile>
8 <Version>3.2.2</Version>
8 <Version>3.3.0</Version>
9 9 <Title>XFEExtension.NetCore.ServerInteractive</Title>
10 10 <RepositoryUrl>https://github.com/XFEstudio/XFEExtension.NetCore.ServerInteractive</RepositoryUrl>
11 11 <AnalysisLevel>latest</AnalysisLevel>
@@ -21,13 +21,11 @@
21 21 <PackageReleaseNotes>
22 22 ## 调整
23 23
24 ServerCoreUserServiceBase属性类型调整为非空
25
26 将 Session 和 DeviceInfo 属性类型由 string? 改为 string,确保获取时必有值并自动校验;项目版本号升级至 3.2.2。
24 核心服务器改为按共享端口统一启动底层监听器,并仅向匹配的核心分发请求及WebSocket事件。
27 25
28 26 ## 新增
29 27
30
28 支持多个核心服务器绑定同一端口,并通过不同的 MainEntryPoint 区分;同端口同入口配置会在启动时直接报错。项目版本号升级至 3.3.0。
31 29
32 30 ## 严重
33 31
@@ -74,4 +72,4 @@
74 72 <None Include="..\XFEExtension.NetCore.ServerInteractive.SourceGenerator\bin\$(Configuration)\netstandard2.0\XFEExtension.NetCore.ServerInteractive.SourceGenerator.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
75 73 </ItemGroup>
76 74
77 </Project>
75 </Project>