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

如何在Java中无缓冲逐字符读取System.in以构建远程Shell?

解决方案:Java实现控制台Raw模式逐字符读取

纯Java确实无法直接实现控制台的raw模式,因为JVM本身不提供切换终端输入模式的API——默认情况下操作系统处于cooked模式,会缓冲输入直到换行符才传递给应用。你需要使用封装了原生终端操作的Java库来解决这个问题,以下是两个可靠的方案:

一、使用JLine4库(推荐)

JLine是Java生态中最成熟的控制台交互库,支持跨平台的终端模式切换,底层封装了原生代码处理Windows、Linux、macOS的终端差异。

1. 添加依赖

如果用Maven,在pom.xml中加入:

<dependency>
    <groupId>org.jline</groupId>
    <artifactId>jline</artifactId>
    <version>3.24.1</version>
</dependency>

2. 逐字符读取示例代码

import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;

public class RawInputHandler {
    public static void main(String[] args) throws IOException {
        // 初始化系统终端
        try (Terminal terminal = TerminalBuilder.builder().system(true).build()) {
            // 切换到raw模式:关闭行缓冲,每个输入字符立即传递给应用
            terminal.enterRawMode();
            System.out.println("已进入raw模式,按Ctrl+C退出...");

            int charCode;
            // 逐字符读取输入
            while ((charCode = terminal.reader().read()) != -1) {
                char inputChar = (char) charCode;
                System.out.printf("读取到字符:%c (ASCII码:%d)%n", inputChar, charCode);
                
                // 在这里将字符发送到远程会话
                // remoteSessionOutputStream.write(charCode);
                // remoteSessionOutputStream.flush();
            }
        }
    }
}

该代码会实时捕获用户输入的每一个字符,无需等待换行符,完全符合你的远程Shell需求。

二、结合pty4J实现终端绑定

既然你已经在使用pty4J,也可以通过创建本地PTY进程,将本地终端与远程会话直接绑定,pty4J会自动处理raw模式和输入转发:

import com.pty4j.PtyProcess;
import com.pty4j.PtyProcessBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class RemoteShellWithPty4J {
    public static void main(String[] args) throws Exception {
        // 假设已建立远程会话(例如通过SSH库获取输出流/输入流)
        OutputStream remoteOut = ...; // 远程会话的输入流(本地写进去,远程接收)
        InputStream remoteIn = ...;  // 远程会话的输出流(远程输出,本地读取)

        // 创建本地PTY进程,关联系统控制台
        PtyProcess localPty = new PtyProcessBuilder()
                .environment(System.getenv())
                .build();

        // 本地PTY输入 -> 远程会话输出
        new Thread(() -> {
            try (InputStream ptyIn = localPty.getInputStream()) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = ptyIn.read(buffer)) != -1) {
                    remoteOut.write(buffer, 0, len);
                    remoteOut.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        // 远程会话输入 -> 本地PTY输出
        new Thread(() -> {
            try (OutputStream ptyOut = localPty.getOutputStream()) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = remoteIn.read(buffer)) != -1) {
                    ptyOut.write(buffer, 0, len);
                    ptyOut.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        // 等待本地PTY进程结束
        localPty.waitFor();
    }
}

这种方式不需要手动处理逐字符读取,pty4J会自动将本地终端切换到raw模式,用户输入会实时转发到远程会话,远程输出也会实时显示在本地控制台。

为什么你之前的方法无效

你尝试的Scanner、FileInputStream等方法,本质上都是从JVM提供的标准输入流读取数据,但在默认的cooked模式下,操作系统会将用户输入缓冲,直到按下换行键才把整行数据传递给JVM。因此这些方法无法绕过操作系统的缓冲机制,必须通过终端库切换到raw模式才能实现实时逐字符读取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 03:20:16