You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Renci SSH.NET在Linux和.Net Core中如何正确使用RemoveForwardedPort()

问题根因

Renci SSH.NET 公开的 SshClient.RemoveForwardedPort() 方法内部默认会先校验SSH连接状态,同时会尝试向服务端发送端口解绑请求,连接断开状态下自然会抛出Client not connected异常;哪怕你后续重连成功,旧的转发端口实例绑定的是之前旧连接的会话上下文,和新连接不匹配,调用公开方法依然会触发校验失败。

可行解决方案

方案1:保留旧SshClient实例,手动清理残留端口

不要在ErrorOccurred或端口异常的回调里直接执行移除操作,异常触发时SshClient可能处于中间状态,容易出现线程安全问题,按以下步骤操作:

  • 先把所有需要清理的旧端口实例存入一个线程安全的队列(比如ConcurrentQueue<ForwardedPortRemote>),单独开清理线程异步处理
  • 先调用端口实例自身的Stop()方法,该方法不需要和服务端交互,连接断开时也可正常执行,会释放本地绑定的端口资源
  • 通过反射绕过公开方法的连接校验,直接移除SshClient内部维护的端口列表里的旧实例,参考代码:
using System.Reflection;
using Renci.SshNet;
using Renci.SshNet.Forwarded;

// 执行移除逻辑
portRemote.Stop();
var forwardedPortsField = typeof(SshClient).GetField("_forwardedPorts", BindingFlags.Instance | BindingFlags.NonPublic);
if (forwardedPortsField != null)
{
    var portList = (IList<ForwardedPort>)forwardedPortsField.GetValue(sshClient);
    portList.Remove(portRemote);
}
// 端口资源会自动被GC回收,不需要额外Dispose

方案2:重连时直接重建SshClient实例(更推荐,稳定性更高)

不需要清理旧实例的残留端口,直接销毁旧实例重建即可,完全避开校验问题:

  • 连接断开触发重连逻辑时,先对旧的SshClient执行Dispose(),释放所有关联的端口、连接资源
  • 重新实例化一个新的SshClient对象,配置好连接参数
  • 把所有需要的转发端口重新注册到新的SshClient实例,再启动连接和端口转发即可
  • 旧端口实例如果要复用,只需要先执行Stop(),再重新绑定到新的SshClient上启动即可,不需要重新创建端口实例

注意事项

  • 所有对SshClient、转发端口的操作都要加互斥锁,避免异常回调、重连逻辑、业务逻辑多线程并发操作导致状态错乱
  • 重连前要确认旧的端口资源已经完全释放,避免出现本地端口被占用的报错

内容的提问来源于stack exchange,提问作者Kris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 04:15:03