先交代下背景。第三方软件发布了命令接口,根据执行发布的命令,可以得到第三方硬件的信息。而我现在需要软件直接执行我排好序的命令,以便获取硬件信息,这时需要做个远程的命令登陆,然后远程执行命令,再退出。这其实就好比要模拟telnet、ftp等的客户端,当远程登陆后执行一个指令,然后返回一大堆执行结果,从而实现与客户端的命令交互。
起先,去下了个common-net.jar的源码看了看,发现telnet、ftp等通信协议都是有专门的消息通道,产生专门的端口来做通讯传输。而这个第三方的命令接口只是个在dos下能执行命令的客户端,再说,即使人家做成了类似telnet的通信协议,俺也不可能知道人家用的端口啥啥的。那现在唯一能做的是,模拟DOS窗口,进行命令的交互。
有了这个思路,就又想到Process可是能执行命令的,那这个是不是可以呢!Process是为了执行命令而产生的一个独立的执行线程。理论上如果只要这个线程不被销毁,那么会一直可以执行命令。可是Process三个流InputStream、OutputStream、ErrorStream在API上说的是提供输出信息的,未知获得了这输入、输出流是否能干出一番事业呢!
1.自动执行命令的交互
public class Demo { public static void main(String[] args) { Process process=null; BufferedOutputStream out=null; BufferedInputStream in=null; try { process=Runtime.getRuntime().exec("sqlplus ethiopia1103/ethiopia1103@db90"); out=new BufferedOutputStream(process.getOutputStream()); in=new BufferedInputStream(process.getInputStream()); out.write("exit".getBytes()); out.write("\r\n".getBytes()); out.flush(); BufferedReader br=new BufferedReader(new InputStreamReader(in)); String line=null; while((line=br.readLine())!=null){ if(line.indexOf(">")!=-1) break; System.out.println(line); } }catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { if(null!=out){ out.close(); out=null; } if(null!=in){ in.close(); in=null; } int value=process.waitFor(); if(null!=process) process.destroy(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
试试直接就退出了,证明传进去的exit命令起作用了。之所以用sqlplus做替代的命令测试,因为其和我要做的第三方接口命令类似。
然后依据这个简单的测试封装了一个命令交互的类。直接上代码:
public class DosCommandInteraction { private static Logger logger=Logger.getLogger(DosCommandInteraction.class); private static final String ENTER="\r\n"; //每次输入命令后回车,然后命令执行,出结果 private static final String END="> "; //遇到>时就退出,证明上一个命令已经执行完 private static final String ERROR="ERROR"; //登录时报ERROR就证明已经登录失败 private Process process=null; private BufferedOutputStream out=null; private BufferedInputStream in=null; /** * 登录到该命令下,创建执行命令的环境进程 * @param command 登陆命令 */ public boolean loggin(String command){ boolean flag=true; try { process=Runtime.getRuntime().exec(command); out=new BufferedOutputStream(process.getOutputStream()); in=new BufferedInputStream(process.getInputStream()); String result=writeCommandResult(in); //把登录时的信息取出来,判断是否登录成功!其实也为后面能正常输出命令结果做了清理 String[] lines=result.split(ENTER); for(String line :lines){ if(line.indexOf(ERROR)!=-1){ flag=false; break; } } }catch (IOException e) { // TODO Auto-generated catch block logger.error(e); close(); } if(!flag) close(); return flag; } /** * 将输入的命令转化为流执行命令得到执行的记录 * @param command * @return */ public List<String> execCommand(String command){ logger.info("exec command : "+command); InputStream input = new ByteArrayInputStream((command+ENTER).getBytes()); readerCommand(out,input); String result=writeCommandResult(in); logger.info(result); try { input.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return Arrays.asList(result.split(ENTER)); } /** * 将命令写入输出流 * @param outs 全局输出流 * @param ins 输入命令的流 */ private void readerCommand(OutputStream outs,InputStream ins){ int ch; try { while ((ch = ins.read()) != -1) { outs.write(ch); outs.flush(); } } catch (IOException e) { close(); logger.error("readerCommand",e); } } /** * 读取命令返回的结果 * @param ins 全局的输入流 * @return 命令结果 */ private String writeCommandResult(InputStream ins){ int length = -1; byte[] buffer = new byte[10240]; String readLine = null; StringBuilder readResult = new StringBuilder(""); try { while((length=ins.read(buffer))>0){ readLine = new String(buffer, 0 , length,"gbk"); readResult.append(readLine); if(readLine.indexOf(ERROR)!=-1) break; if(readResult.toString().endsWith(END)||readResult.toString().endsWith(END.trim())) break; } } catch (IOException e) { close(); logger.error("writeCommandResult",e); } return readResult.toString(); } /** * 所有命令执行完成后推出命令,关闭进程 */ public void quit(){ execCommand("quit"); close(); } /** * 关闭所有的流和进程 */ private void close(){ try { if(null!=out){ out.close(); out=null; } if(null!=in){ in.close(); in=null; } int value=process.waitFor(); logger.info("process end state :" +value); if(null!=process) process.destroy(); } catch (IOException e) { // TODO Auto-generated catch block logger.error(e); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
我在配置文件里设置如下:
<bean id="command" class="java.util.ArrayList"> <constructor-arg> <list> <value>sqlplus orcl/orcl@db</value> <value>select 1 from dual;</value> <value>select 2 from dual;</value> <value>select 3 from dual;</value> <value>select 4 from dual;</value> </list> </constructor-arg> </bean>
测试方法:
public void handler(){ List<String> commList=(List<String>) SpringUtil.getObject("command"); logger.info("start................."); DosCommandInteraction dos=new DosCommandInteraction(); if(!dos.loggin(commList.get(0))){ logger.info("connection error!"); return; } for(int i=1;i<commList.size();i++) dos.execCommand(commList.get(i)); dos.quit(); logger.info("end................."); }
测试结果如下:
main [2011-06-27 16:33:59] - start................. main [2011-06-27 16:33:59] - exec command : select 1 from dual; main [2011-06-27 16:33:59] - 1 ---------- 1 SQL> main [2011-06-27 16:33:59] - exec command : select 2 from dual; main [2011-06-27 16:33:59] - 2 ---------- 2 SQL> main [2011-06-27 16:33:59] - exec command : select 3 from dual; main [2011-06-27 16:33:59] - 3 ---------- 3 SQL> main [2011-06-27 16:33:59] - exec command : select 4 from dual; main [2011-06-27 16:33:59] - 4 ---------- 4 SQL> main [2011-06-27 16:33:59] - exec command : quit main [2011-06-27 16:33:59] - 从 Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning, OLAP and Data Mining options 断开 main [2011-06-27 16:33:59] - process end state :0 main [2011-06-27 16:33:59] - end.................
真正做到了自动执行命令,并且获取到该命令的结果。
2.如果是想直接敲命令的互动,可是尝试如下:
public class TwoDemo { public static void main(String[] args) { Process process = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { process = Runtime.getRuntime().exec( "sqlplus orcl/orcl@db"); out = new BufferedOutputStream(process.getOutputStream()); in = new BufferedInputStream(process.getInputStream()); readWrite(in, out, System.in, System.out); //该方法借用common-net的测试例子部分的一个工具类的方法 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (null != out) { out.close(); out = null; } if (null != in) { in.close(); in = null; } int value = process.waitFor(); if (null != process) process.destroy(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static final void readWrite(final InputStream remoteInput, final OutputStream remoteOutput, final InputStream localInput, final OutputStream localOutput) { Thread reader, writer; reader = new Thread() { @Override public void run() { int ch; try { while (!interrupted() && (ch = localInput.read()) != -1) { remoteOutput.write(ch); remoteOutput.flush(); } } catch (IOException e) { // e.printStackTrace(); } } }; writer = new Thread() { @Override public void run() { try { Util.copyStream(remoteInput, localOutput); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } }; writer.setPriority(Thread.currentThread().getPriority() + 1); writer.start(); reader.setDaemon(true); reader.start(); try { writer.join(); reader.interrupt(); } catch (InterruptedException e) { } } }
看看测试结果:
SQL*Plus: Release 10.2.0.1.0 - Production on 星期一 6月 27 16:45:58 2011 Copyright (c) 1982, 2005, Oracle. All rights reserved. 连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning, OLAP and Data Mining options SQL> select * from dual; DU -- X SQL> select 1 from dual; 1 ---------- 1 SQL> exit 从 Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning, OLAP and Data Mining options 断开
和在dos下执行是完全一样的
产生process = Runtime.getRuntime().exec()时为什么要选择这种方式呢?为什么不是ProcessBuilder了呢?
我们现在不论使用哪种方式产生的命令执行进程都会读取进程的流,所以就不会再有流堵塞而导致无法执行下去的问题了。
ProcessBuilder在加入命令时是以数组的形式,如果是sqlplus orcl/orcl@db就需要分为两个参数加入,而现在我们更希望是一个命令是一个字符串。
有什么问题希望指正。