【发布时间】:2010-12-03 09:41:19
【问题描述】:
我有一个在 Tomcat 中运行的 Java 程序,它需要在本地机器上执行几个 ssh 和 scp 命令,以及一些简单的命令,例如 ls。我目前的方法遇到问题,因为每次执行 ssh 命令时都会超时。我可以在命令行上毫无问题地运行 ssh 命令,但是当它从我的 Java 程序执行时,它会超时。我正在运行以 root 身份执行 ssh 命令的 Web 应用程序(即,我以 root 用户身份启动 Tomcat,并将我的 Web 应用程序代码部署为 WAR 文件),据我所知,两者都配置了正确的认证密钥本地和远程机器,至少我可以在命令行以 root 身份执行 ssh 命令,而无需输入用户名或密码。我没有在我的 Java 程序正在执行的 ssh 命令中指定用户名或密码,因为我假设我可以在我的 Java 代码中运行与我可以在命令行中执行的相同的 ssh 命令,但也许这是错误的假设并且是我麻烦的原因。
我开发的执行命令的Java代码如下:
public class ProcessUtility
{
static Log log = LogFactory.getLog(ProcessUtility.class);
/**
* Thread class to be used as a worker
*/
private static class Worker
extends Thread
{
private final Process process;
private volatile Integer exitValue;
Worker(final Process process)
{
this.process = process;
}
public Integer getExitValue()
{
return exitValue;
}
@Override
public void run()
{
try
{
exitValue = process.waitFor();
}
catch (InterruptedException ignore)
{
return;
}
}
}
/**
* Executes a command.
*
* @param args command + arguments
*/
public static void execCommand(final String[] args)
{
try
{
Runtime.getRuntime().exec(args);
}
catch (IOException e)
{
// swallow it
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
public static int executeCommand(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
return executeCommandWithWorker(command, printOutput, printError, timeOut);
}
/**
* Executes a command and returns its output or error stream.
*
* @param command
* @return the command's resulting output or error stream
*/
public static String executeCommandReceiveOutput(final String command)
{
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec(command);
try
{
// consume the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", false);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", false);
outputGobbler.start();
errorGobbler.start();
// execute the command
if (process.waitFor() == 0)
{
return outputGobbler.getInput();
}
return errorGobbler.getInput();
}
finally
{
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
@SuppressWarnings("unused")
private static int executeCommandWithExecutors(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
// validate the system and command line and get a system-appropriate command line
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec(massagedCommand);
// consume and display the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
// create a Callable for the command's Process which can be called by an Executor
Callable<Integer> call = new Callable<Integer>()
{
public Integer call()
throws Exception
{
process.waitFor();
return process.exitValue();
}
};
// submit the command's call via an Executor and get the result from a Future
ExecutorService executorService = Executors.newSingleThreadExecutor();
try
{
Future<Integer> futureResultOfCall = executorService.submit(call);
int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS);
return exitValue;
}
catch (TimeoutException ex)
{
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (ExecutionException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
finally
{
executorService.shutdown();
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
private static int executeCommandWithWorker(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
// validate the system and command line and get a system-appropriate command line
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(massagedCommand);
// consume and display the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
// create and start a Worker thread which this thread will join for the timeout period
Worker worker = new Worker(process);
worker.start();
try
{
worker.join(timeOut);
Integer exitValue = worker.getExitValue();
if (exitValue != null)
{
// the worker thread completed within the timeout period
// stop the output and error stream gobblers
outputGobbler.stopGobbling();
errorGobbler.stopGobbling();
return exitValue;
}
// if we get this far then we never got an exit value from the worker thread as a result of a timeout
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
catch (InterruptedException ex)
{
worker.interrupt();
Thread.currentThread().interrupt();
throw ex;
}
finally
{
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Validates that the system is running a supported OS and returns a system-appropriate command line.
*
* @param originalCommand
* @return
*/
private static String validateSystemAndMassageCommand(final String originalCommand)
{
// make sure that we have a command
if (originalCommand.isEmpty() || (originalCommand.length() < 1))
{
String errorMessage = "Missing or empty command line parameter.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
// make sure that we are running on a supported system, and if so set the command line appropriately
String massagedCommand;
String osName = System.getProperty("os.name");
if (osName.equals("Windows XP"))
{
massagedCommand = "cmd.exe /C " + originalCommand;
}
else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux"))
{
massagedCommand = originalCommand;
}
else
{
String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" +
osName + "\').";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
return massagedCommand;
}
}
class StreamGobbler
extends Thread
{
static private Log log = LogFactory.getLog(StreamGobbler.class);
private InputStream inputStream;
private String streamType;
private boolean displayStreamOutput;
private final StringBuffer inputBuffer = new StringBuffer();
private boolean keepGobbling = true;
/**
* Constructor.
*
* @param inputStream the InputStream to be consumed
* @param streamType the stream type (should be OUTPUT or ERROR)
* @param displayStreamOutput whether or not to display the output of the stream being consumed
*/
StreamGobbler(final InputStream inputStream,
final String streamType,
final boolean displayStreamOutput)
{
this.inputStream = inputStream;
this.streamType = streamType;
this.displayStreamOutput = displayStreamOutput;
}
/**
* Returns the output stream of the
*
* @return
*/
public String getInput()
{
return inputBuffer.toString();
}
/**
* Consumes the output from the input stream and displays the lines consumed if configured to do so.
*/
@Override
public void run()
{
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
try
{
String line = null;
while (keepGobbling && inputStreamReader.ready() && ((line = bufferedReader.readLine()) != null))
{
inputBuffer.append(line);
if (displayStreamOutput)
{
System.out.println(streamType + ">" + line);
}
}
}
catch (IOException ex)
{
log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
ex.printStackTrace();
}
finally
{
try
{
bufferedReader.close();
inputStreamReader.close();
}
catch (IOException e)
{
// swallow it
}
}
}
public void stopGobbling()
{
keepGobbling = false;
}
}
我在我的 Java 程序中执行 ssh 命令,如下所示:
ProcessUtility.executeCommand("ssh " + physicalHostIpAddress + " virsh list \| grep " + newDomUName, false, false, 3600000)
谁能看到我做错了什么?顺便说一句,上面的代码是使用本文作为指南开发的:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html。我不是并发编程方面的专家,所以也许我正在做一些愚蠢的事情——如果是,请随时指出。
非常感谢您提供任何建议、想法等。
--詹姆斯
更新:我现在听取了帮助我的人的建议,他们回答了我最初的问题,并编写了一个类,该类提供了进行 ssh 和 scp 调用的方法,使用两个 Java ssh 库 jsch 实现(jsch-0.1.31) 和 sshtools (j2ssh-core-0.2.9)。然而,这些实现都没有工作,因为它们都在连接步骤中失败,在我有机会执行身份验证之前。我希望我在运行代码的服务器上遇到某种配置问题,尽管这并不明显,因为当我在这些服务器上发出 ssh 或 scp 命令时,我可以毫无问题地在这些服务器上执行命令行。由于 ssh -V,我正在测试代码的 Solaris 服务器显示以下内容:
Sun_SSH_1.3,SSH 协议 1.5/2.0,OpenSSL 0x0090801f
以下是我为此目的编写的 Java 代码——如果有人能看出我在 Java 代码级别做错了什么,请告诉我,如果是这样,非常感谢您的帮助。
public class SecureCommandUtility
{
static Log log = LogFactory.getLog(SecureCommandUtility.class);
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param username
* @param password
* @param remoteHost
* @param remoteFilePathName
* @param timeout
*/
public static void secureCopySingleFile(final String localFilePathName,
final String username,
final String password,
final String remoteHost,
final String remoteFilePathName,
final int timeout)
{
// basic validation of the parameters
if ((localFilePathName == null) || localFilePathName.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied local file path name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((username == null) || username.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied user name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((password == null) || password.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied password parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteHost == null) || remoteHost.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied remote host parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteFilePathName == null) || remoteFilePathName.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied remote file path name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if (timeout < 1000)
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied timeout parameter is less than one second.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
//secureCopySingleFileJSch(localFilePathName, username, password, remoteHost, remoteFilePathName);
secureCopySingleFileJ2Ssh(localFilePathName, username, password, remoteHost, remoteFilePathName, timeout);
}
/**
*
* @param user
* @param password
* @param remoteHost
* @param command
* @return exit status of the command
*/
public static int secureShellCommand(final String user,
final String password,
final String remoteHost,
final String command,
final int timeout)
{
// basic validation of the parameters
if ((user == null) || user.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied user name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((password == null) || password.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied password parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteHost == null) || remoteHost.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied remote host parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((command == null) || command.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command: the supplied command parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if (timeout < 1000)
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied timeout parameter is less than one second.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
//return secureShellCommandJsch(user, password, remoteHost, command, timeout);
return secureShellCommandJ2Ssh(user, password, remoteHost, command, timeout);
}
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param username
* @param password
* @param remoteHost
* @param remoteFilePathName
* @param timeout
*/
private static void secureCopySingleFileJ2Ssh(final String localFilePathName,
final String username,
final String password,
final String remoteHost,
final String remoteFilePathName,
final int timeout)
{
SshClient sshClient = null;
try
{
// create and connect client
sshClient = new SshClient();
sshClient.setSocketTimeout(timeout);
sshClient.connect(remoteHost, 22, new IgnoreHostKeyVerification());
// perform password-based authentication
PasswordAuthenticationClient passwordAuthenticationClient = new PasswordAuthenticationClient();
passwordAuthenticationClient.setUsername(username);
passwordAuthenticationClient.setPassword(password);
if (sshClient.authenticate(passwordAuthenticationClient) != AuthenticationProtocolState.COMPLETE)
{
// log the error and throw an exception
String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
remoteFilePathName + "\' -- failed to authenticate using username/password \'" +
username + "\'/\'" + password + "\'.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
// perform the copy
sshClient.openScpClient().put(localFilePathName, remoteFilePathName, false);
}
catch (Exception ex)
{
// log the error and throw an exception
String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
remoteFilePathName + "\'.";
log.error(errorMessage, ex);
throw new LifecycleException(errorMessage, ex);
}
finally
{
if ((sshClient != null) && sshClient.isConnected())
{
sshClient.disconnect();
}
}
}
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param user
* @param password
* @param remoteHost
* @param remoteFilePathName
*/
@SuppressWarnings("unused")
private static void secureCopySingleFileJSch(final String localFilePathName,
final String user,
final String password,
final String remoteHost,
final String remoteFilePathName)
{
Session session = null;
Channel channel = null;
FileInputStream fileInputStream = null;
try
{
// create and connect Jsch session
JSch jsch = new JSch();
session = jsch.getSession(user, remoteHost, 22);
session.setPassword(password);
session.connect();
// exec 'scp -p -t remoteFilePathName' remotely
String command = "scp -p -t " + remoteFilePathName;
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
// get the I/O streams for the remote scp
OutputStream outputStream = channel.getOutputStream();
InputStream inputStream = channel.getInputStream();
// connect the channel
channel.connect();
int ackCheck = checkAck(inputStream);
if (checkAck(inputStream) != 0)
{
// log the error and throw an exception
String errorMessage = "The scp command failed -- input stream ACK check failed with the following result: " +
ackCheck;
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
// send "C0644 filesize filename", where filename should not include '/'
long filesize = (new File(localFilePathName)).length();
command = "C0644 " + filesize + " ";
if (localFilePathName.lastIndexOf('/') > 0)
{
command += localFilePathName.substring(localFilePathName.lastInde
【问题讨论】:
-
您究竟在哪里获得了时间? '
process.waitFor();' ??指定它。
标签: java process ssh runtime command