From 91fac3a01f324fc1125af34a58adca1127c14963 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 2 Sep 2026 23:42:01 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor(tcp-forward):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20TCP=20=E8=BD=AC=E5=8F=91=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=AE=A9?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E5=B7=A5=E4=BD=9C=E6=9B=B4=E5=8A=A0=E7=A8=B3?= =?UTF-8?q?=E5=81=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PCL.Core/IO/Net/NetworkHelper.cs | 8 +- PCL.Core/IO/Net/SocketExtension.cs | 20 +- .../IO/Net/SocketForward/TcpForwardBuilder.cs | 89 +++++++ .../IO/Net/SocketForward/TcpForwardConfig.cs | 16 ++ .../IO/Net/SocketForward/TcpForwardWorker.cs | 204 ++++++++++++++++ PCL.Core/IO/Net/TcpForward.cs | 222 ------------------ PCL.Core/Link/BroadcastLocal.cs | 4 +- PCL.Core/Link/Lobby/LobbyController.cs | 10 +- PCL.Core/Link/Lobby/LobbyInfoProvider.cs | 3 +- 9 files changed, 339 insertions(+), 237 deletions(-) create mode 100644 PCL.Core/IO/Net/SocketForward/TcpForwardBuilder.cs create mode 100644 PCL.Core/IO/Net/SocketForward/TcpForwardConfig.cs create mode 100644 PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs delete mode 100644 PCL.Core/IO/Net/TcpForward.cs diff --git a/PCL.Core/IO/Net/NetworkHelper.cs b/PCL.Core/IO/Net/NetworkHelper.cs index e8938e9bc4..ebf00c7973 100644 --- a/PCL.Core/IO/Net/NetworkHelper.cs +++ b/PCL.Core/IO/Net/NetworkHelper.cs @@ -8,11 +8,9 @@ public static class NetworkHelper { public static int NewTcpPort() { - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; + using var so = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + so.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + return so.LocalEndPoint == null ? 0 : ((IPEndPoint)so.LocalEndPoint).Port; } public static bool IsNetworkAvailable() diff --git a/PCL.Core/IO/Net/SocketExtension.cs b/PCL.Core/IO/Net/SocketExtension.cs index 42ef9844a6..c4668d30bd 100644 --- a/PCL.Core/IO/Net/SocketExtension.cs +++ b/PCL.Core/IO/Net/SocketExtension.cs @@ -4,18 +4,30 @@ namespace PCL.Core.IO.Net; public static class SocketExtensions { - public static void SafeClose(this Socket? socket) + public static void CloseGracefully(this Socket? socket) { if (socket is null) return; - try + if (socket is { IsBound: false, Connected: false }) + { + socket.Dispose(); + return; + } + + if (socket.Connected) { - if (socket.Connected) + try { - socket.Shutdown(SocketShutdown.Both); + socket.Shutdown(SocketShutdown.Both); } + catch { /* 忽略关闭时的任何错误 */ } + } + + try + { socket.Close(); } catch { /* 忽略关闭时的任何错误 */ } + } } \ No newline at end of file diff --git a/PCL.Core/IO/Net/SocketForward/TcpForwardBuilder.cs b/PCL.Core/IO/Net/SocketForward/TcpForwardBuilder.cs new file mode 100644 index 0000000000..5dfac3b9fe --- /dev/null +++ b/PCL.Core/IO/Net/SocketForward/TcpForwardBuilder.cs @@ -0,0 +1,89 @@ +using System; +using System.Net; +using PCL.Core.Utils.Exts; + +namespace PCL.Core.IO.Net.SocketForward; + +public class TcpForwardBuilder +{ + private readonly TcpForwardConfig _cfg = new(); + + public TcpForwardWorker Build() + { + ArgumentNullException.ThrowIfNull(_cfg.RemoteHost); + ArgumentOutOfRangeException.ThrowIfZero(_cfg.RemotePort); + + if (_cfg.LocalHost.IsNullOrWhiteSpace()) _cfg.LocalHost = IPAddress.Loopback.ToString(); + + return new TcpForwardWorker(_cfg); + } + + public TcpForwardBuilder BindLocalRandom() + { + _cfg.LocalHost = IPAddress.Loopback.ToString(); + _cfg.LocalPort = 0; + + return this; + } + + public TcpForwardBuilder BindLocal(ushort port) + { + ArgumentOutOfRangeException.ThrowIfZero(port); + + _cfg.LocalHost = IPAddress.Loopback.ToString(); + _cfg.LocalPort = port; + + return this; + } + + public TcpForwardBuilder SetRemote(string host, ushort port) + { + ArgumentException.ThrowIfNullOrWhiteSpace(host); + ArgumentOutOfRangeException.ThrowIfZero(port); + + _cfg.RemoteHost = host; + _cfg.RemotePort = port; + + return this; + } + + public TcpForwardBuilder SetRemote(IPEndPoint remote) + { + ArgumentNullException.ThrowIfNull(remote); + + _cfg.RemoteHost = remote.Address.ToString(); + _cfg.RemotePort = (ushort) remote.Port; + + return this; + } + + public TcpForwardBuilder SetRemote(IPAddress host, ushort port) + { + ArgumentNullException.ThrowIfNull(host); + ArgumentOutOfRangeException.ThrowIfZero(port); + + _cfg.RemoteHost = host.ToString(); + _cfg.RemotePort = port; + + return this; + } + + public TcpForwardBuilder SetBufferSize(uint size) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(size, TcpForwardConfig.MaxBufferSize); + ArgumentOutOfRangeException.ThrowIfLessThan(size, TcpForwardConfig.MinBufferSize); + + _cfg.BufferSize = size; + + return this; + } + + public TcpForwardBuilder SetMaxAllowedActiveConnection(ushort maxConnectionCount) + { + ArgumentOutOfRangeException.ThrowIfZero(maxConnectionCount); + + _cfg.MaxConnection = maxConnectionCount; + + return this; + } +} \ No newline at end of file diff --git a/PCL.Core/IO/Net/SocketForward/TcpForwardConfig.cs b/PCL.Core/IO/Net/SocketForward/TcpForwardConfig.cs new file mode 100644 index 0000000000..773a00cd0d --- /dev/null +++ b/PCL.Core/IO/Net/SocketForward/TcpForwardConfig.cs @@ -0,0 +1,16 @@ +using System.Net; + +namespace PCL.Core.IO.Net.SocketForward; + +public sealed class TcpForwardConfig +{ + public string? LocalHost { get; set; } + public ushort LocalPort { get; set; } + public string? RemoteHost { get; set; } + public ushort RemotePort { get; set; } + public ushort MaxConnection { get; set; } = 10; + + public const uint MaxBufferSize = 32*1024; // 32 KB + public const uint MinBufferSize = 1024; // 1 KB + public uint BufferSize { get; set; } = 8192; +} \ No newline at end of file diff --git a/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs new file mode 100644 index 0000000000..e1c04bffdd --- /dev/null +++ b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs @@ -0,0 +1,204 @@ +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using PCL.Core.Logging; + +namespace PCL.Core.IO.Net.SocketForward; +public sealed class TcpForwardWorker : IDisposable +{ + private readonly TcpForwardConfig _cfg; + private volatile CancellationTokenSource _cts; + private readonly SemaphoreSlim _connectionSemaphore; + private Task? _workerTask; + private readonly ConcurrentDictionary _subWorkerTask = []; + private const string ModuleName = "TcpForward"; + + internal TcpForwardWorker(TcpForwardConfig cfg) + { + _cfg = cfg; + _cts = new CancellationTokenSource(); + _connectionSemaphore = new SemaphoreSlim(_cfg.MaxConnection, _cfg.MaxConnection); + } + + public IPEndPoint? LocalEndPoint { get; private set; } + + public int ActiveConnectionCount => _cfg.MaxConnection - _connectionSemaphore.CurrentCount; + private readonly Lock _operationLock = new(); + + public void Start() + { + lock (_operationLock) + { + if (_workerTask is { IsCompleted: false }) return; + + _cts = new CancellationTokenSource(); + _workerTask = _WorkerFunc(); + _workerTask.ContinueWith(x => + { + if (x.IsFaulted) + LogWrapper.Error(x.Exception, ModuleName, "工作线程出现错误"); + }); + } + } + + public void Stop() + { + lock (_operationLock) + { + if (_workerTask is not { IsCompleted: false }) return; + _cts.Cancel(); + var oldCts = _cts; + // ReSharper disable once MethodSupportsCancellation + _ = Task.WhenAll([_workerTask, .. _subWorkerTask.Values]).ContinueWith(x => + { + oldCts.Dispose(); + }); + LogWrapper.Info(ModuleName, "TCP 端口转发已停止,转发线程将在后台陆续关闭"); + _subWorkerTask.Clear(); + } + } + + private async Task _WorkerFunc() + { + using var listener = new Socket(SocketType.Stream, ProtocolType.Tcp); + listener.NoDelay = true; + listener.ReceiveBufferSize = (int)_cfg.BufferSize; + listener.SendBufferSize = (int)_cfg.BufferSize; + + if (!IPAddress.TryParse(_cfg.LocalHost, out var localAddress)) + throw new InvalidOperationException("出现意料之外的本地监听地址"); + listener.Bind(new IPEndPoint(localAddress, _cfg.LocalPort)); + listener.Listen(); + + // 暴露给外部用 + if (listener.LocalEndPoint is not IPEndPoint endPoint) throw new InvalidCastException("出现了意外的转换操作"); + LocalEndPoint = endPoint; + + LogWrapper.Info(ModuleName, $"TCP 端口转发已启动,监听 {endPoint},目标 tcp://{_cfg.RemoteHost}:{_cfg.RemotePort}"); + + while (!_cts.IsCancellationRequested) + { + try + { + var clientSocket = await listener.AcceptAsync(_cts.Token).ConfigureAwait(false); + + if (await _connectionSemaphore.WaitAsync(0).ConfigureAwait(false)) // 是否还能创建新连接 + { + // 投递给转发线程 + var taskGuid = Guid.NewGuid(); + _subWorkerTask.TryAdd(taskGuid, _HandleConnectionAsync(clientSocket, _cts.Token) + .ContinueWith(_ => + { + try + { + _subWorkerTask.TryRemove(taskGuid, out _); + _connectionSemaphore.Release(); + } catch (ObjectDisposedException) {/* ignore */} + })); + } + else + { + clientSocket.CloseGracefully(); + LogWrapper.Warn(ModuleName, $"已达到最大连接数限制({_cfg.MaxConnection}),拒绝新连接"); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + LogWrapper.Error(ex, ModuleName, $"接受连接时发生错误"); + await Task.Delay(500).ConfigureAwait(false); + } + } + } + + private async Task _HandleConnectionAsync(Socket clientSocket, CancellationToken cancellationToken) + { + var connectionId = Guid.NewGuid(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + Socket? targetSocket = null; + + try + { + LogWrapper.Info(ModuleName, $"接受来自 {clientSocket.RemoteEndPoint} 的连接"); + + // 连接到目标服务器 + targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + targetSocket.NoDelay = true; + targetSocket.ReceiveBufferSize = (int)_cfg.BufferSize; + targetSocket.SendBufferSize = (int)_cfg.BufferSize; + + await targetSocket.ConnectAsync(_cfg.RemoteHost!, _cfg.RemotePort, cancellationToken).ConfigureAwait(false); + + LogWrapper.Info(ModuleName, $"开始 TCP 转发 {clientSocket.RemoteEndPoint} <-> {targetSocket.RemoteEndPoint}({connectionId})"); + + // 使用高性能的 SocketAsyncEventArgs 进行双向转发 + var forwardTask1 = _ForwardDataAsync(clientSocket, targetSocket, _cfg.BufferSize, cts.Token); + var forwardTask2 = _ForwardDataAsync(targetSocket, clientSocket, _cfg.BufferSize, cts.Token); + + // 等待任意一个方向的数据转发完成 + await Task.WhenAny(forwardTask1, forwardTask2).ConfigureAwait(false); + await cts.CancelAsync().ConfigureAwait(false); + + LogWrapper.Debug(ModuleName, $"TCP 转发 {connectionId} 已结束"); + } + catch (OperationCanceledException) + { + // 取消操作,正常退出 + } + catch (Exception ex) + { + LogWrapper.Error(ex, ModuleName, $"处理连接 {connectionId} 时发生错误"); + } + finally + { + clientSocket.CloseGracefully(); + targetSocket?.CloseGracefully(); + } + } + + private static async Task _ForwardDataAsync(Socket source, Socket destination, uint bufferSize, CancellationToken cancellationToken) + { + using var bufferOwner = MemoryPool.Shared.Rent((int)bufferSize); + try + { + var buffer = bufferOwner.Memory; + while (!cancellationToken.IsCancellationRequested) + { + var bytesRead = await source.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); + if (bytesRead == 0) break; // 连接已关闭 + + await destination.SendAsync(buffer[..bytesRead], SocketFlags.None, cancellationToken).ConfigureAwait(false); + } + } + catch {/* 忽略错误 */} + } + + private bool _disposed; + + public void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + + private void _Dispose(bool disposing) + { + if (!disposing) return; + if (_disposed) return; + Stop(); + _connectionSemaphore.Dispose(); + _disposed = true; + } + + ~TcpForwardWorker() + { + _Dispose(false); + } +} \ No newline at end of file diff --git a/PCL.Core/IO/Net/TcpForward.cs b/PCL.Core/IO/Net/TcpForward.cs deleted file mode 100644 index efca59aa12..0000000000 --- a/PCL.Core/IO/Net/TcpForward.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Concurrent; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using PCL.Core.Logging; - -namespace PCL.Core.IO.Net; -public sealed class TcpForward( - IPAddress listenAddress, - int listenPort, - IPAddress targetAddress, - int targetPort, - int maxConnections = 10) - : IDisposable -{ - private Socket? _listenerSocket; - private CancellationTokenSource? _cts; - private readonly SemaphoreSlim _connectionSemaphore = new(maxConnections, maxConnections); - private readonly ConcurrentDictionary _activeConnections = new(); - - private bool _isRunning; - - public int LocalPort { get; private set; } - - public int ActiveConnections => _activeConnections.Count; - - public void Start() - { - if (_isRunning) return; - - _cts = new CancellationTokenSource(); - _isRunning = true; - - try - { - // 创建并启动监听 Socket - _listenerSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) - { - NoDelay = true, // 禁用 Nagle 算法以提高响应速度 - ReceiveBufferSize = 8192, - SendBufferSize = 8192 - }; - - _listenerSocket.Bind(new IPEndPoint(listenAddress, listenPort)); - _listenerSocket.Listen(100); // 设置挂起连接队列的最大长度 - - if (_listenerSocket.LocalEndPoint is not IPEndPoint endPoint) throw new InvalidCastException("出现了意外的转换操作"); - LocalPort = endPoint.Port; - - // 启动 TCP 接受连接任务 - _ = Task.Run(() => _AcceptConnectionsAsync(_cts.Token), _cts.Token); - - LogWrapper.Info("TcpForward", $"MC 端口转发已启动,监听 {listenAddress}:{LocalPort},目标 {targetAddress}:{targetPort}"); - } - catch (Exception ex) - { - _isRunning = false; - LogWrapper.Error(ex, "TcpForward", $"启动 MC 端口转发时发生错误: {ex.Message}"); - throw; - } - } - - public void Stop() - { - if (!_isRunning) return; - - _cts?.Cancel(); - _isRunning = false; - - // 关闭所有活动连接 - foreach (var connection in _activeConnections.Values) - { - connection.ClientSocket.SafeClose(); - connection.TargetSocket.SafeClose(); - } - _activeConnections.Clear(); - - _listenerSocket?.SafeClose(); - - LogWrapper.Info("TcpForward", "MC 端口转发已停止"); - } - - private async Task _AcceptConnectionsAsync(CancellationToken cancellationToken) - { - cancellationToken.Register(() => - { - _listenerSocket.SafeClose(); - }); - while (!cancellationToken.IsCancellationRequested) - { - try - { - if (_listenerSocket is null) break; - var clientSocket = await _listenerSocket.AcceptAsync(cancellationToken); - - // 检查是否达到最大连接限制 - if (_activeConnections.Count >= maxConnections) - { - clientSocket.SafeClose(); - LogWrapper.Warn("TcpForward", $"已达到最大连接数限制({maxConnections}),拒绝新连接"); - continue; - } - - // 使用信号量控制并发处理 - await _connectionSemaphore.WaitAsync(cancellationToken); - - // 异步处理连接,不等待完成 - _ = Task.Run(() => _HandleConnectionAsync(clientSocket, cancellationToken), cancellationToken) - .ContinueWith(_ => _connectionSemaphore.Release(), TaskScheduler.Default); - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - LogWrapper.Error(ex, "TcpForward", $"接受连接时发生错误"); - await Task.Delay(1000, cancellationToken); // 出错后等待 1 秒再继续 - } - } - } - - private async Task _HandleConnectionAsync(Socket clientSocket, CancellationToken cancellationToken) - { - var connectionId = Guid.NewGuid(); - - try - { - LogWrapper.Info("TcpForward", $"接受来自 {clientSocket.RemoteEndPoint} 的连接"); - - // 连接到目标服务器 - var targetSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) - { - NoDelay = true, - ReceiveBufferSize = 8192, - SendBufferSize = 8192 - }; - - await targetSocket.ConnectAsync(targetAddress, targetPort, cancellationToken); - - // 保存连接对 - var connectionPair = new ConnectionPair(clientSocket, targetSocket); - _activeConnections[connectionId] = connectionPair; - - LogWrapper.Info("TcpForward", $"开始端口转发 {clientSocket.RemoteEndPoint} <-> {targetSocket.RemoteEndPoint}({connectionId})"); - - // 使用高性能的 SocketAsyncEventArgs 进行双向转发 - var forwardTask1 = _ForwardDataAsync(clientSocket, targetSocket, cancellationToken); - var forwardTask2 = _ForwardDataAsync(targetSocket, clientSocket, cancellationToken); - - // 等待任意一个方向的数据转发完成 - await Task.WhenAny(forwardTask1, forwardTask2); - - Console.WriteLine($"端口转发 {connectionId} 已完成"); - } - catch (OperationCanceledException) - { - // 取消操作,正常退出 - } - catch (Exception ex) - { - Console.WriteLine($"处理连接 {connectionId} 时发生错误: {ex.Message}"); - } - finally - { - // 清理资源 - clientSocket.SafeClose(); - - // 从活动连接中移除 - _activeConnections.TryRemove(connectionId, out _); - } - } - - private static async Task _ForwardDataAsync(Socket source, Socket destination, CancellationToken cancellationToken) - { - using var bufferOwner = MemoryPool.Shared.Rent(8192); - try - { - var buffer = bufferOwner.Memory; - while (!cancellationToken.IsCancellationRequested) - { - var bytesRead = await source.ReceiveAsync(buffer, SocketFlags.None, cancellationToken); - if (bytesRead == 0) break; // 连接已关闭 - - await destination.SendAsync(buffer[..bytesRead], SocketFlags.None, cancellationToken); - } - } - catch {/* 忽略错误 */} - } - - private bool _disposed; - - public void Dispose() - { - _Dispose(true); - GC.SuppressFinalize(this); - } - - private void _Dispose(bool disposing) - { - if (!disposing) return; - if (_disposed) return; - Stop(); - _cts?.Dispose(); - _connectionSemaphore.Dispose(); - _disposed = true; - } - - ~TcpForward() - { - _Dispose(false); - } - - private class ConnectionPair(Socket clientSocket, Socket targetSocket) - { - public Socket ClientSocket { get; } = clientSocket; - public Socket TargetSocket { get; } = targetSocket; - } -} \ No newline at end of file diff --git a/PCL.Core/Link/BroadcastLocal.cs b/PCL.Core/Link/BroadcastLocal.cs index 8c0ff42605..2053d9440e 100644 --- a/PCL.Core/Link/BroadcastLocal.cs +++ b/PCL.Core/Link/BroadcastLocal.cs @@ -34,7 +34,7 @@ public void Stop() _cts?.Cancel(); _isRunning = false; - _broadcastSocket?.SafeClose(); + _broadcastSocket?.CloseGracefully(); Console.WriteLine("停止向本地 Minecraft 客户端广播"); } @@ -80,7 +80,7 @@ public void Dispose() { Stop(); _cts?.Dispose(); - _broadcastSocket.SafeClose(); + _broadcastSocket.CloseGracefully(); GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/PCL.Core/Link/Lobby/LobbyController.cs b/PCL.Core/Link/Lobby/LobbyController.cs index 4abbb56e98..2eb946149a 100644 --- a/PCL.Core/Link/Lobby/LobbyController.cs +++ b/PCL.Core/Link/Lobby/LobbyController.cs @@ -22,6 +22,7 @@ using LobbyType = PCL.Core.Link.Scaffolding.Client.Models.LobbyType; using PCL.Core.Link.McPing; using PCL.Core.IO.Net.Http; +using PCL.Core.IO.Net.SocketForward; namespace PCL.Core.Link.Lobby; @@ -91,11 +92,14 @@ public sealed class LobbyController var desc = hostname.IsNullOrWhiteSpace() ? string.Empty : Lang.Text("Link.Lobby.MotdDesc", hostname); - var tcpPortForForward = NetworkHelper.NewTcpPort(); - McForward = new TcpForward(IPAddress.Loopback, tcpPortForForward, IPAddress.Loopback, localPort); - McBroadcast = new BroadcastLocal(Lang.Text("Link.Lobby.MotdFormat", desc), tcpPortForForward); + var tcpPortForForward = NetworkHelper.NewTcpPort(); + McForward = new TcpForwardBuilder() + .BindLocal((ushort)tcpPortForForward) + .SetRemote(IPAddress.Loopback, (ushort)tcpPortForForward) + .Build(); McForward.Start(); + McBroadcast = new BroadcastLocal(Lang.Text("Link.Lobby.MotdFormat", desc), tcpPortForForward); McBroadcast.Start(); return scfEntity; diff --git a/PCL.Core/Link/Lobby/LobbyInfoProvider.cs b/PCL.Core/Link/Lobby/LobbyInfoProvider.cs index 9d0390f195..ff322925c6 100644 --- a/PCL.Core/Link/Lobby/LobbyInfoProvider.cs +++ b/PCL.Core/Link/Lobby/LobbyInfoProvider.cs @@ -2,6 +2,7 @@ using System.Numerics; using PCL.Core.App; using PCL.Core.IO.Net; +using PCL.Core.IO.Net.SocketForward; using PCL.Core.Link.Natayark; using PCL.Core.Logging; using PCL.Core.Utils; @@ -18,7 +19,7 @@ public static class LobbyInfoProvider public static int ProtocolVersion { get; set; } = 6; public static BroadcastLocal? McBroadcast { get; internal set; } - public static TcpForward? McForward { get; internal set; } + public static TcpForwardWorker? McForward { get; internal set; } public class LobbyInfo { From 630921747a75e5b1111990268184d8928564660e Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 2 Sep 2026 23:52:20 +0800 Subject: [PATCH 2/4] fix: compile error --- .../Pages/PageTools/PageToolsGameLink.xaml.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Plain Craft Launcher 2/Pages/PageTools/PageToolsGameLink.xaml.cs b/Plain Craft Launcher 2/Pages/PageTools/PageToolsGameLink.xaml.cs index 211feee14d..340a53b5fa 100644 --- a/Plain Craft Launcher 2/Pages/PageTools/PageToolsGameLink.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageTools/PageToolsGameLink.xaml.cs @@ -970,7 +970,9 @@ private void BtnFinishCopy_Click(object sender, ModBase.RouteEventArgs routeEven // 复制 IP private void BtnFinishCopyIp_Click(object sender, ModBase.RouteEventArgs routeEventArgs) { - var ip = $"127.0.0.1:{LobbyInfoProvider.McForward.LocalPort}"; + var port = LobbyInfoProvider.McForward?.LocalEndPoint?.Port; + if (port == null) return; + var ip = $"127.0.0.1:{port}"; ModMain.MyMsgBox(Lang.Text("Tools.GameLink.CopyIp.Message", ip), Lang.Text("Tools.GameLink.CopyIp.Title"), Lang.Text("Common.Action.Copy"), From f0cc82e6c57b1e93a8db7e943b52099d085b3016 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Thu, 3 Sep 2026 00:05:15 +0800 Subject: [PATCH 3/4] fix: SendAsync may not enough --- PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs | 13 +++++++++++-- PCL.Core/Link/Lobby/LobbyController.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs index e1c04bffdd..b7a7d3480f 100644 --- a/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs +++ b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs @@ -166,15 +166,24 @@ private async Task _HandleConnectionAsync(Socket clientSocket, CancellationToken private static async Task _ForwardDataAsync(Socket source, Socket destination, uint bufferSize, CancellationToken cancellationToken) { using var bufferOwner = MemoryPool.Shared.Rent((int)bufferSize); + var buffer = bufferOwner.Memory; + try { - var buffer = bufferOwner.Memory; while (!cancellationToken.IsCancellationRequested) { var bytesRead = await source.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); if (bytesRead == 0) break; // 连接已关闭 - await destination.SendAsync(buffer[..bytesRead], SocketFlags.None, cancellationToken).ConfigureAwait(false); + var bytesSend = 0; + while (bytesRead > bytesSend) + { + var currentSend= await destination.SendAsync(buffer[bytesSend..bytesRead], SocketFlags.None, cancellationToken).ConfigureAwait(false); + if (currentSend == 0) break; // 对端关闭 + bytesSend += currentSend; + } + + if (bytesRead != bytesSend) break; // 外层关闭 } } catch {/* 忽略错误 */} diff --git a/PCL.Core/Link/Lobby/LobbyController.cs b/PCL.Core/Link/Lobby/LobbyController.cs index 2eb946149a..0c80630c04 100644 --- a/PCL.Core/Link/Lobby/LobbyController.cs +++ b/PCL.Core/Link/Lobby/LobbyController.cs @@ -96,7 +96,7 @@ public sealed class LobbyController var tcpPortForForward = NetworkHelper.NewTcpPort(); McForward = new TcpForwardBuilder() .BindLocal((ushort)tcpPortForForward) - .SetRemote(IPAddress.Loopback, (ushort)tcpPortForForward) + .SetRemote(IPAddress.Loopback, (ushort)localPort) .Build(); McForward.Start(); McBroadcast = new BroadcastLocal(Lang.Text("Link.Lobby.MotdFormat", desc), tcpPortForForward); From 49b3c66280c6df63a99eac7ecdfd76edcc87b498 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Thu, 3 Sep 2026 10:14:51 +0800 Subject: [PATCH 4/4] chore: resolve warnings --- PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs index b7a7d3480f..589036d197 100644 --- a/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs +++ b/PCL.Core/IO/Net/SocketForward/TcpForwardWorker.cs @@ -56,6 +56,7 @@ public void Stop() _ = Task.WhenAll([_workerTask, .. _subWorkerTask.Values]).ContinueWith(x => { oldCts.Dispose(); + if (x.IsFaulted) LogWrapper.Error(x.Exception, ModuleName, "后台关闭工作线程时遇到错误抛出"); }); LogWrapper.Info(ModuleName, "TCP 端口转发已停止,转发线程将在后台陆续关闭"); _subWorkerTask.Clear(); @@ -91,8 +92,9 @@ private async Task _WorkerFunc() // 投递给转发线程 var taskGuid = Guid.NewGuid(); _subWorkerTask.TryAdd(taskGuid, _HandleConnectionAsync(clientSocket, _cts.Token) - .ContinueWith(_ => + .ContinueWith(x => { + if (x.IsFaulted) LogWrapper.Error(x.Exception, ModuleName, "连接处理线程出现错误"); try { _subWorkerTask.TryRemove(taskGuid, out _); @@ -178,7 +180,8 @@ private static async Task _ForwardDataAsync(Socket source, Socket destination, u var bytesSend = 0; while (bytesRead > bytesSend) { - var currentSend= await destination.SendAsync(buffer[bytesSend..bytesRead], SocketFlags.None, cancellationToken).ConfigureAwait(false); + var currentSend= await destination.SendAsync(buffer[bytesSend..bytesRead], SocketFlags.None, cancellationToken) + .ConfigureAwait(false); if (currentSend == 0) break; // 对端关闭 bytesSend += currentSend; }