在 Java JSch Shell 中得到两次 shell 提示

Getting shell prompt twice in Java JSch Shell

好的,我遇到了 shell 的响应非常不一致的问题。 例如,欢迎消息可以来自 2 个单独的响应。出于某种原因,当我之后使用任何命令时,提示将在我的响应中出现两次。

我注意到只有在使用我自己的管道 input/output 流时才会出现这些问题。当我使用 System.inSystem.out 时,这些问题根本不会发生。

这是我使用 System.inSystem.out 的图片:

使用我自己的管道 input/output 流时:

相关代码下方

SSHConnection.java

package com.displee.ssh;

import com.jcraft.jsch.*;

import java.util.function.Function;

public class SSHConnection {

    private static final JSch JSCH_INSTANCE = new JSch();

    private Session session;

    private ShellSession shellSession;

    public SSHConnection(String ip, String username, String password) {
        try {
            this.session = JSCH_INSTANCE.getSession(username, ip);
            this.session.setPassword(password);
            this.session.setConfig("StrictHostKeyChecking", "no");
        } catch(Exception e) {
            e.printStackTrace();
            this.session = null;
        }
    }

    public boolean connect() {
        try {
            session.connect();
            return true;
        } catch(Exception e) {
            e.printStackTrace();
            session.disconnect();
            return false;
        }
    }

    public boolean isConnected() {
        return session.isConnected();
    }

    public void disconnect() {
        session.disconnect();
    }

    public boolean startShellSession(Function<String, Void> callback) {
        try {
            shellSession = new ShellSession();
            shellSession.start(session, callback);
            return true;
        } catch(Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public void writeShellMessage(String message) {
        shellSession.write(message + "\r\n");
    }

}

ShellSession.java

package com.displee.ssh;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.Session;

import java.io.*;
import java.util.function.Function;

/**
 * A class representing a shell session.
 * @author Displee
 */
public class ShellSession {

    /**
     * If this session is running.
     */
    private boolean running = true;

    private PipedOutputStream poutWrapper;

    private PipedInputStream pin = new PipedInputStream(4096);

    private PipedInputStream pinWrapper = new PipedInputStream(4096);

    private PipedOutputStream pout;

    private String lastCommand;

    /**
     * Constructs a new {@code ShellSession} {@code Object}.
     */
    public ShellSession() {
        try {
            pout = new PipedOutputStream(pinWrapper);
            poutWrapper = new PipedOutputStream(pin);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Start this shell session.
     * @param session The ssh session.
     */
    public void start(Session session, Function<String, Void> callback) {
        ChannelShell shellChannel = null;
        try {
            shellChannel = (ChannelShell) session.openChannel("shell");
            shellChannel.setInputStream(pin);
            shellChannel.setOutputStream(pout);
            shellChannel.connect();
        } catch(Exception e) {
            e.printStackTrace();
        }
        if (shellChannel == null) {
            return;
        }
        final Channel channel = shellChannel;
        Thread thread = new Thread(() -> {
            while (running) {
                try {
                    if (pinWrapper.available() != 0) {
                        String response = readResponse();
                        callback.apply(response);
                    }
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            channel.disconnect();
        });
        thread.setDaemon(true);
        thread.start();
    }

    /**
     * Stop this shell session.
     */
    public void stop() {
        running = false;
    }

    /**
     * Send a message to the shell.
     * @param message The message to send.
     */
    public void write(String message) {
        lastCommand = message;
        try {
            poutWrapper.write(message.getBytes());
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Read the response from {@code pinWrapper}.
     * @return The string.
     * @throws IOException If it could not read the piped stream.
     */
    private synchronized String readResponse() throws IOException {
        final StringBuilder s = new StringBuilder();
        while(pinWrapper.available() > 0) {
            s.append((char) pinWrapper.read());
        }
        return s.toString();
    }

}

我的 JavaFX 控制器:

package com.displee.ui;

import com.displee.ssh.SSHConnection;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {

    @FXML
    private TextArea console;

    @FXML
    private TextField commandInput;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        SSHConnection connection = new SSHConnection("host", "username", "password");
        connection.connect();
        connection.startShellSession(s -> {
            console.appendText(s);
            return null;
        });
        commandInput.setOnAction(x -> {
            connection.writeShellMessage(commandInput.getText());
            commandInput.clear();
        });
    }

}

完整代码:https://displee.com/upload/SSHProject.zip

我尝试增加输入流的大小,但没有用。

这个问题真让我沮丧。 如果有人知道如何解决我的问题,我将不胜感激。

注意:我还注意到使用管道流时速度很慢。我怎样才能加快速度?

您是否正确处理 CR (carriage return)

服务器可能在所有情况下都会发送两次提示。但是,如果两个提示之间有 CR,当在 System.out 上打印时,第二个提示将覆盖第一个。如果您的流将 CR 解释为 "new-line" (=line feed),您会收到两个提示。