XFEExtension.NetCore.ServerInteractive
[DLL] Server interaction extension, including user identity verification and querying in conjunction with AutoConfig
关注
0
Fork
0
Star
0
返回提交历史
Modified
README.md
+26
-0
Modified
README.zh-CN.md
+27
-1
Modified
XFEExtension.NetCore.ServerInteractive.TServer/Program.cs
+3
-3
Modified
XFEExtension.NetCore.ServerInteractive.Test/Program.cs
+2
-2
Modified
XFEExtension.NetCore.ServerInteractive/Options/XFEServerCoreOptions.cs
+2
-1
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Server/Services/ServerService/XFEServerCoreProcessService.cs
+33
-45
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServer.cs
+2
-1
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerBuilder.cs
+1
-0
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCore.cs
+100
-29
Modified
XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCoreBuilder.cs
+1
-6
Added
XFEExtension.NetCore.ServerInteractive/Utilities/Server/XFEServerCoreHost.cs
+234
-0
Modified
XFEExtension.NetCore.ServerInteractive/XFEExtension.NetCore.ServerInteractive.csproj
+4
-6
XFEstudio/XFEExtension.NetCore.ServerInteractive
支持多核心同端口绑定及主入口点分发
实现 XFEServerCoreHost,支持同端口多核心服务器通过不同 MainEntryPoint 隔离与分发 HTTP/WebSocket 事件。XFEServerCore 启动与请求处理逻辑调整,支持入口点规范化、路由提取和优先级判定。XFEServerCoreBuilder 构建时自动规范化入口点。XFEServerCoreProcessService 启动流程优化,异常重启逻辑增强。CyberCommServer 事件绑定调整为多核心共享。补充多核心同端口用法及路由分发规则文档。版本号升级至 3.3.0,优化调试输出和异常信息,兼容旧用法。
99b68ae
代码差异
12 个文件
+435
-94
@@ -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
@@ -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
- 确认服务端路由与请求名称完全一致(区分大小写)
@@ -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(); // 启动服务
@@ -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
}
@@ -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
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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>