JSch SSH隧道通信:正确建立本地端口转发与直连通道

秋涛同学_6005

秋涛同学_6005

2026-05-26

301人浏览

原创

JSch SSH隧道通信:正确建立本地端口转发与直连通道

本文详解如何在Java/Scala中使用JSch库稳定构建SSH隧道,支持向远程服务持续发送认证数据并接收流式响应,重点解决channel is not opened和session is down等典型初始化异常。

本文详解如何在java/scala中使用jsch库稳定构建ssh隧道,支持向远程服务持续发送认证数据并接收流式响应,重点解决`channel is not opened`和`session is down`等典型初始化异常。

在使用 JSch 实现 SSH 隧道通信时,常见误区是混淆两种互补但不可混用的隧道机制:本地端口转发(setPortForwardingL) 与 直连 TCP/IP 通道(direct-tcpip 或 getStreamForwarder)。二者本质不同:前者在本地暴露一个真实 TCP 端口供外部进程(如 nc)连接;后者则在 JVM 内部直接建立加密隧道通道,无需中间 Socket。若错误组合(例如调用 setPortForwardingL 后又试图打开 direct-tcpip 通道),或遗漏关键配置步骤,将必然触发 JSchException("channel is not opened") 或 "session is down"。

✅ 推荐方案一:使用 setPortForwardingL + Socket(类 Bash 行为)

该方式完全复刻你的 Bash 命令逻辑,适合需与现有工具链集成或调试验证的场景:

JSch jsch = new JSch();
jsch.addIdentity(privateKeyPath);
Session session = jsch.getSession(username, remoteHost, 22);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();

// ✅ 正确顺序:先设置端口转发,再创建 Socket
int localPort = 8080; // 任意可用本地端口
int remotePort = 9000; // 远程服务监听端口
String remoteTarget = "127.0.0.1"; // 远程主机上目标地址(通常为 localhost)

// 启动本地端口转发(阻塞式,返回实际分配的端口)
int assignedPort = session.setPortForwardingL(localPort, remoteTarget, remotePort);

// ✅ 确保隧道已就绪后再创建 Socket
Socket socket = new Socket("localhost", assignedPort);
socket.setSoTimeout(30000); // 设置读超时,避免阻塞

OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
PrintWriter writer = new PrintWriter(out, true); // auto-flush

// 发送认证 JSON(注意换行符!netcat 依赖 \n 分隔)
String authJson = "{\"id\": 1234, \"auth\": \"abcdefghijklmnopqrstuvwxyz\"}";
writer.println(authJson);

// 持续读取远程服务推送的多行响应
String line;
while ((line = reader.readLine()) != null) {
    System.out.println("Received: " + line);
    // 可在此处添加每10秒重发认证逻辑(如用 ScheduledExecutorService)
}
socket.close();
session.disconnect();

⚠️ 关键注意事项:

dmap-jsapi-skill
dmap-jsapi-skill

DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。

下载
  • setPortForwardingL() 必须在 session.connect() 之后调用;
  • Socket 必须在 setPortForwardingL() 返回后立即创建,不要等待延迟——JSch 后台线程会自动维护隧道,但首次连接需及时触发;
  • 若指定 localPort=0,JSch 将自动分配空闲端口,返回值即为实际端口;
  • StrictHostKeyChecking="no" 仅用于开发,生产环境应通过 KnownHosts 文件校验主机密钥。

✅ 推荐方案二:使用 getStreamForwarder(更高效、纯 Java 方式)

当全部逻辑均在 JVM 内完成(无外部 nc 进程),此方案更轻量、可控性更强:

Session session = jsch.getSession(username, remoteHost, 22);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();

// ✅ 直接获取流式转发通道(目标为远程服务地址+端口)
Channel channel = session.getStreamForwarder(remoteHost, remotePort); // 注意:此处 remoteHost 是远程服务所在主机(非跳板机IP!)
// 若服务在远程主机的 loopback 上,且 SSH 登录用户可直连,则用 "127.0.0.1"
// channel = session.getStreamForwarder("127.0.0.1", remotePort);

// ✅ 必须显式 connect(),且必须在 session 保持活跃状态下执行
channel.connect();

InputStream in = channel.getInputStream();
OutputStream out = channel.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
PrintWriter writer = new PrintWriter(out, true);

writer.println("{\"id\": 1234, \"auth\": \"abcdefghijklmnopqrstuvwxyz\"}");

// 持续读取
String line;
while ((line = reader.readLine()) != null) {
    System.out.println("Remote: " + line);
}
channel.disconnect();
session.disconnect();

? 为什么原代码失败?

  • getStreamForwarder 报 "session is down":因 channel.connect() 调用前,session 可能已被意外关闭(如异常提前退出),或 connect() 在 session 失效后调用;
  • direct-tcpip 报 "channel is not opened":因未调用 setHost()/setPort() 设置目标地址,或在 connect() 前尝试 I/O —— direct-tcpip 通道必须手动配置目标:
    Channel channel = session.openChannel("direct-tcpip");
    ((ChannelDirectTCPIP) channel).setHost("127.0.0.1"); // 必须设置
    ((ChannelDirectTCPIP) channel).setPort(9000);         // 必须设置
    channel.connect(); // 此后才能 getInputStream/getOutputStream

? 总结与最佳实践

场景 推荐方式 优势 注意点
模拟 Bash ssh -L + nc setPortForwardingL + Socket 兼容性强,便于调试 确保 Socket 创建及时;避免端口冲突
纯 Java 内部通信 getStreamForwarder 无额外端口开销,性能更高 目标地址需准确(常为 "127.0.0.1");严格保证 session.connect() → channel.connect() → I/O 的时序
需动态目标或复杂路由 direct-tcpip 完全可控目标地址 必须显式 setHost/setPort,且仅适用于支持 SSH 直连的目标

最后提醒:始终在 try-with-resources 或 finally 块中确保 channel.disconnect() 和 session.disconnect() 被调用,防止连接泄漏。结合 ScheduledExecutorService 实现周期性认证发送,即可完整复现你的 Bash 流式交互逻辑。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

3289

3

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

2023.07.25

2348

9

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

2023.08.02

1280

5

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.09

1238

4

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

2023.09.05

1376

5

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2218

7

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

2023.09.20

3580

8

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

2023.09.22

16515

6

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

549

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 21.1万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.9万人学习