Servlet用URL调用批处理?怎么调啊。Runtime.exec应该可以取输出的。

解决方案 »

  1.   

    呵呵,不是啊,只是我想以网页方式取代通过共享目录运行程序的模式
    --比如:通过链接编译服务器上的全部源码
    Runtime.exec看得不深,了解不够透彻:(
    唉!
      

  2.   

    这个就应该用Runtime.exec啊,Tomcat就是这么做的,估计Ant也是。
      

  3.   

    是啊,只是两个东西看起来都挺费劲的,而且我所要执行的命令行主要是针对ant的,所以拿不定主意:)
    急功近利了,而且想要现成一点的,呵呵,多谢指点
      

  4.   

    没有这么干过,我想可能取不到Process的XXXputStream();应该是不在本地的原因吧。如果实在不行,可以换用socket试试,传简单的命令给服务器,让服务器来分析该干什么,然后得到输出信息,在把这个信息传回客户
      

  5.   

    是不在本地,在接收请求的服务器上,我是这样设想的:
    在客户端点击链接,执行服务器上的一系列操作:从Cvs上取版本、单元测试、编译、发布到网站等
      

  6.   

    呵呵,我说的本地是指Java运行的地方与运行Process的地方是一台机器,也就是你的服务器,是这样把。 xioyoo的意思好像是你从客户端运行Java,调服务器上的批处理。
      

  7.   

    Socket方案还是太麻烦,唉:(
      

  8.   

    既然能用HTTP,就没必要用Socket了,当然前提是服务器上有Servlet引擎。
      

  9.   

    用ant很简单的哦,你看一下ant的文档啦,
    他有一个telnet的option命令的,可以实现
    你的要求,不过要下载一个option的jar,和
    一个新的ant的properties的,文档上面有说的
    自己看一下吧
      

  10.   

    在Java程序中截获控制台输出    
     
     
       
    内容: 
     
    一、Java管道流  
      1.1 注意事项一  
      1.2 注意事项二  
      1.3 注意事项三  
      1.4 解决问题  
    二、捕获Java控制台输出  
    三、捕获其他程序的控制台输出 
    参考资料 
    关于作者 
     
     
     
     
    俞良松 ([email protected])
    软件工程师,独立顾问和自由撰稿人
    2001 年 10 月在Java开发中,控制台输出仍是一个重要的工具,但默认的控制台输出有着各种各样的局限。本文介绍如何用Java管道流截取控制台输出,分析管道流应用中应该注意的问题,提供了截取Java程序和非Java程序控制台输出的实例。
    即使在图形用户界面占统治地位的今天,控制台输出仍旧在Java程序中占有重要地位。控制台不仅是Java程序默认的堆栈跟踪和错误信息输出窗口,而且还是一种实用的调试工具(特别是对习惯于使用println()的人来说)。然而,控制台窗口有着许多局限。例如在Windows 9x平台上,DOS控制台只能容纳50行输出。如果Java程序一次性向控制台输出大量内容,要查看这些内容就很困难了。对于使用javaw这个启动程序的开发者来说,控制台窗口尤其宝贵。因为用javaw启动java程序时,根本不会有控制台窗口出现。如果程序遇到了问题并抛出异常,根本无法查看Java运行时环境写入到System.out或System.err的调用堆栈跟踪信息。为了捕获堆栈信息,一些人采取了用try/catch()块封装main()的方式,但这种方式不一定总是有效,在Java运行时的某些时刻,一些描述性错误信息会在抛出异常之前被写入System.out和System.err;除非能够监测这两个控制台流,否则这些信息就无法看到。因此,有些时候检查Java运行时环境(或第三方程序)写入到控制台流的数据并采取合适的操作是十分必要的。本文讨论的主题之一就是创建这样一个输入流,从这个输入流中可以读入以前写入Java控制台流(或任何其他程序的输出流)的数据。我们可以想象写入到输出流的数据立即以输入的形式“回流”到了Java程序。本文的目标是设计一个基于Swing的文本窗口显示控制台输出。在此期间,我们还将讨论一些和Java管道流(PipedInputStream和PipedOutputStream)有关的重要注意事项。图一显示了用来截取和显示控制台文本输出的Java程序,用户界面的核心是一个JTextArea。最后,我们还要创建一个能够捕获和显示其他程序(可以是非Java的程序)控制台输出的简单程序。
    图一:多线程的控制台输出截取程序一、Java管道流
    要在文本框中显示控制台输出,我们必须用某种方法“截取”控制台流。换句话说,我们要有一种高效地读取写入到System.out和System.err所有内容的方法。如果你熟悉Java的管道流PipedInputStream和PipedOutputStream,就会相信我们已经拥有最有效的工具。写入到PipedOutputStream输出流的数据可以从对应的PipedInputStream输入流读取。Java的管道流极大地方便了我们截取控制台输出。Listing 1显示了一种非常简单的截取控制台输出方案。【Listing 1:用管道流截取控制台输出】
    PipedInputStream pipedIS = new PipedInputStream();
    PipedOutputStream pipedOS = new PipedOutputStream();
    try {
       pipedOS.connect(pipedIS);
    }
    catch(IOException e) {
       System.err.println("连接失败");
       System.exit(1);
    }
    PrintStream ps = new PrintStream(pipedOS);
    System.setOut(ps);
    System.setErr(ps);
     可以看到,这里的代码极其简单。我们只是建立了一个PipedInputStream,把它设置为所有写入控制台流的数据的最终目的地。所有写入到控制台流的数据都被转到PipedOutputStream,这样,从相应的PipedInputStream读取就可以迅速地截获所有写入控制台流的数据。接下来的事情似乎只剩下在Swing JTextArea中显示从pipedIS流读取的数据,得到一个能够在文本框中显示控制台输出的程序。遗憾的是,在使用Java管道流时有一些重要的注意事项。只有认真对待所有这些注意事项才能保证Listing 1的代码稳定地运行。下面我们来看第一个注意事项。1.1 注意事项一
    PipedInputStream运用的是一个1024字节固定大小的循环缓冲区。写入PipedOutputStream的数据实际上保存到对应的PipedInputStream的内部缓冲区。从PipedInputStream执行读操作时,读取的数据实际上来自这个内部缓冲区。如果对应的PipedInputStream输入缓冲区已满,任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞,直至出现读取PipedInputStream的操作从缓冲区删除数据。这意味着,向PipedOutputStream写数据的线程不应该是负责从对应PipedInputStream读取数据的唯一线程。从图二可以清楚地看出这里的问题所在:假设线程t是负责从PipedInputStream读取数据的唯一线程;另外,假定t企图在一次对PipedOutputStream的write()方法的调用中向对应的PipedOutputStream写入2000字节的数据。在t线程阻塞之前,它最多能够写入1024字节的数据(PipedInputStream内部缓冲区的大小)。然而,一旦t被阻塞,读取PipedInputStream的操作就再也不会出现,因为t是唯一读取PipedInputStream的线程。这样,t线程已经完全被阻塞,同时,所有其他试图向PipedOutputStream写入数据的线程也将遇到同样的情形。
    图二:管道流工作过程这并不意味着在一次write()调用中不能写入多于1024字节的数据。但应当保证,在写入数据的同时,有另一个线程从PipedInputStream读取数据。Listing 2示范了这个问题。这个程序用一个线程交替地读取PipedInputStream和写入PipedOutputStream。每次调用write()向PipedInputStream的缓冲区写入20字节,每次调用read()只从缓冲区读取并删除10个字节。内部缓冲区最终会被写满,导致写操作阻塞。由于我们用同一个线程执行读、写操作,一旦写操作被阻塞,就不能再从PipedInputStream读取数据。【Listing 2:用同一个线程执行读/写操作导致线程阻塞】import java.io.*;
    public class Listing2 {
        static PipedInputStream pipedIS = new PipedInputStream();
        static PipedOutputStream pipedOS = 
            new PipedOutputStream();    public static void main(String[] a){
            try {
                pipedIS.connect(pipedOS);
            }
            catch(IOException e) {
                System.err.println("连接失败");
                    System.exit(1);
                }        byte[] inArray    = new byte[10];
            byte[] outArray = new byte[20];
            int bytesRead = 0;        try {
                // 向pipedOS发送20字节数据
                pipedOS.write(outArray, 0, 20);
                System.out.println("     已发送20字节...");           // 在每一次循环迭代中,读入10字节
               // 发送20字节
                bytesRead = pipedIS.read(inArray, 0, 10);
                int i=0;
                while(bytesRead != -1) {
                    pipedOS.write(outArray, 0, 20);
                    System.out.println("     已发送20字节..."+i);
                    i++;
                    bytesRead = pipedIS.read(inArray, 0, 10);
                }
            }
            catch(IOException e) {
                    System.err.println("读取pipedIS时出现错误: " + e);
                    System.exit(1);
            }
        } // main()
    } 只要把读/写操作分开到不同的线程,Listing 2的问题就可以轻松地解决。Listing 3是Listing 2经过修改后的版本,它在一个单独的线程中执行写入PipedOutputStream的操作(和读取线程不同的线程)。为证明一次写入的数据可以超过1024字节,我们让写操作线程每次调用PipedOutputStream的write()方法时写入2000字节。那么,在startWriterThread()方法中创建的线程是否会阻塞呢?按照Java运行时线程调度机制,它当然会阻塞。写操作在阻塞之前实际上最多只能写入1024字节的有效载荷(即PipedInputStream缓冲区的大小)。但这并不会成为问题,因为主线程(main)很快就会从PipedInputStream的循环缓冲区读取数据,空出缓冲区空间。最终,写操作线程会从上一次中止的地方重新开始,写入2000字节有效载荷中的剩余部分。【Listing 3:把读/写操作分开到不同的线程】
    import java.io.*;public class Listing3 {
        static PipedInputStream pipedIS =
            new PipedInputStream();
        static PipedOutputStream pipedOS =
            new PipedOutputStream();    public static void main(String[] args) {
            try {
                pipedIS.connect(pipedOS);
            }
            catch(IOException e) {
                System.err.println("连接失败");
                System.exit(1);
            }        byte[] inArray = new byte[10];
            int bytesRead = 0;        // 启动写操作线程
            startWriterThread();        try {
                bytesRead = pipedIS.read(inArray, 0, 10);
                while(bytesRead != -1) {
                    System.out.println("已经读取" +
                        bytesRead + "字节...");
                    bytesRead = pipedIS.read(inArray, 0, 10);
                }
            }
            catch(IOException e) {
                System.err.println("读取输入错误.");
                System.exit(1);
            }
        } // main()    // 创建一个独立的线程
        // 执行写入PipedOutputStream的操作
        private static void startWriterThread() {
            new Thread(new Runnable() {
                public void run() {
                    byte[] outArray = new byte[2000];                while(true
      

  11.   

    接上:1.4 解决问题
    要防止管道流前两个局限所带来的问题,方法之一是用一个ByteArrayOutputStream作为代理或替代PipedOutputStream。Listing 4显示了一个LoopedStreams类,它用一个ByteArrayOutputStream提供和Java管道流类似的功能,但不会出现死锁和IOException异常。这个类的内部仍旧使用管道流,但隔离了本文介绍的前两个问题。我们先来看看这个类的公用方法(参见图3)。构造函数很简单,它连接管道流,然后调用startByteArrayReaderThread()方法(稍后再讨论该方法)。getOutputStream()方法返回一个OutputStream(具体地说,是一个ByteArrayOutputStream)用以替代PipedOutputStream。写入该OutputStream的数据最终将在getInputStream()方法返回的流中作为输入出现。和使用PipedOutputStream的情形不同,向ByteArrayOutputStream写入数据的线程的激活、写数据、结束不会带来负面效果。
    图三:ByteArrayOutputStream原理
    【Listing 4:防止管道流应用中出现的常见问题】
    import java.io.*;public class LoopedStreams {
        private PipedOutputStream pipedOS = 
            new PipedOutputStream();
        private boolean keepRunning = true;
        private ByteArrayOutputStream byteArrayOS =
            new ByteArrayOutputStream() {
            public void close() {
                keepRunning = false;
                try {
                    super.close();
                    pipedOS.close();
                }
                catch(IOException e) {
                    // 记录错误或其他处理
                    // 为简单计,此处我们直接结束
                    System.exit(1);
                }
            }
        };
        private PipedInputStream pipedIS = new PipedInputStream() {
            public void close() {
                keepRunning = false;
                try    {
                    super.close();
                }
                catch(IOException e) {
                    // 记录错误或其他处理
                    // 为简单计,此处我们直接结束
                    System.exit(1);
                }
            }
        };
        public LoopedStreams() throws IOException {
            pipedOS.connect(pipedIS);
            startByteArrayReaderThread();
        } // LoopedStreams()
        public InputStream getInputStream() {
            return pipedIS;
        } // getInputStream()
        public OutputStream getOutputStream() {
            return byteArrayOS;
        } // getOutputStream()
        private void startByteArrayReaderThread() {
            new Thread(new Runnable() {
                public void run() {
                    while(keepRunning) {
                        // 检查流里面的字节数
                        if(byteArrayOS.size() > 0) {
                            byte[] buffer = null;
                            synchronized(byteArrayOS) {
                                buffer = byteArrayOS.toByteArray();
                                byteArrayOS.reset(); // 清除缓冲区
                            }
                            try {
                                // 把提取到的数据发送给PipedOutputStream
                                pipedOS.write(buffer, 0, buffer.length);
                            }
                            catch(IOException e) {
                                // 记录错误或其他处理
                                // 为简单计,此处我们直接结束
                                System.exit(1);
                            }
                        }
                        else // 没有数据可用,线程进入睡眠状态
                            try {
                                // 每隔1秒查看ByteArrayOutputStream检查新数据
                                Thread.sleep(1000);
                            }
                            catch(InterruptedException e) {}
                        }
                 }
            }).start();
        } // startByteArrayReaderThread()
    } // LoopedStreams startByteArrayReaderThread()方法是整个类真正的关键所在。这个方法的目标很简单,就是创建一个定期地检查ByteArrayOutputStream缓冲区的线程。缓冲区中找到的所有数据都被提取到一个byte数组,然后写入到PipedOutputStream。由于PipedOutputStream对应的PipedInputStream由getInputStream()返回,从该输入流读取数据的线程都将读取到原先发送给ByteArrayOutputStream的数据。前面提到,LoopedStreams类解决了管道流存在的前二个问题,我们来看看这是如何实现的。ByteArrayOutputStream具有根据需要扩展其内部缓冲区的能力。由于存在“完全缓冲”,线程向getOutputStream()返回的流写入数据时不会被阻塞。因而,第一个问题不会再给我们带来麻烦。另外还要顺便说一句,ByteArrayOutputStream的缓冲区永远不会缩减。例如,假设在能够提取数据之前,有一块500 K的数据被写入到流,缓冲区将永远保持至少500 K的容量。如果这个类有一个方法能够在数据被提取之后修正缓冲区的大小,它就会更完善。第二个问题得以解决的原因在于,实际上任何时候只有一个线程向PipedOutputStream写入数据,这个线程就是由startByteArrayReaderThread()创建的线程。由于这个线程完全由LoopedStreams类控制,我们不必担心它会产生IOException异常。LoopedStreams类还有一些细节值得提及。首先,我们可以看到byteArrayOS和pipedIS实际上分别是ByteArrayOutputStream和PipedInputStream的派生类的实例,也即在它们的close()方法中加入了特殊的行为。如果一个LoopedStreams对象的用户关闭了输入或输出流,在startByteArrayReaderThread()中创建的线程必须关闭。覆盖后的close()方法把keepRunning标记设置成false以关闭线程。另外,请注意startByteArrayReaderThread()中的同步块。要确保在toByteArray()调用和reset()调用之间ByteArrayOutputStream缓冲区不被写入流的线程修改,这是必不可少的。由于ByteArrayOutputStream的write()方法的所有版本都在该流上同步,我们保证了ByteArrayOutputStream的内部缓冲区不被意外地修改。注意LoopedStreams类并不涉及管道流的第三个问题。该类的getInputStream()方法返回PipedInputStream。如果一个线程从该流读取,一段时间后终止,下次数据从ByteArrayOutputStream缓冲区传输到PipedOutputStream时就会出现IOException异常。
      

  12.   

    接上:二、捕获Java控制台输出
    Listing 5的ConsoleTextArea类扩展Swing JTextArea捕获控制台输出。不要对这个类有这么多代码感到惊讶,必须指出的是,ConsoleTextArea类有超过50%的代码用来进行测试。【Listing 5:截获Java控制台输出】
    import java.io.*;
    import java.util.*;
    import javax.swing.*;
    import javax.swing.text.*;public class ConsoleTextArea extends JTextArea {
        public ConsoleTextArea(InputStream[] inStreams) {
            for(int i = 0; i < inStreams.length; ++i)
                startConsoleReaderThread(inStreams[i]);
        } // ConsoleTextArea()    public ConsoleTextArea() throws IOException {
            final LoopedStreams ls = new LoopedStreams();        // 重定向System.out和System.err
            PrintStream ps = new PrintStream(ls.getOutputStream());
            System.setOut(ps);
            System.setErr(ps);        startConsoleReaderThread(ls.getInputStream());
        } // ConsoleTextArea()
        private void startConsoleReaderThread(
            InputStream inStream) {
            final BufferedReader br =
                new BufferedReader(new InputStreamReader(inStream));
            new Thread(new Runnable() {
                public void run() {
                    StringBuffer sb = new StringBuffer();
                    try {
                        String s;
                        Document doc = getDocument();
                        while((s = br.readLine()) != null) {
                            boolean caretAtEnd = false;
                            caretAtEnd = getCaretPosition() == doc.getLength() ?
                                true : false;
                            sb.setLength(0);
                            append(sb.append(s).append('\n').toString());
                            if(caretAtEnd)
                                setCaretPosition(doc.getLength());
                        }
                    }
                    catch(IOException e) {
                        JOptionPane.showMessageDialog(null,
                            "从BufferedReader读取错误:" + e);
                        System.exit(1);
                    }
                }
            }).start();
        } // startConsoleReaderThread()
        // 该类剩余部分的功能是进行测试
        public static void main(String[] args) {
            JFrame f = new JFrame("ConsoleTextArea测试");
            ConsoleTextArea consoleTextArea = null;        try {
                consoleTextArea = new ConsoleTextArea();
            }
            catch(IOException e) {
                System.err.println(
                    "不能创建LoopedStreams:" + e);
                System.exit(1);
            }        consoleTextArea.setFont(java.awt.Font.decode("monospaced"));
            f.getContentPane().add(new JScrollPane(consoleTextArea),
                java.awt.BorderLayout.CENTER);
            f.setBounds(50, 50, 300, 300);
            f.setVisible(true);        f.addWindowListener(new java.awt.event.WindowAdapter() {
                public void windowClosing(
                    java.awt.event.WindowEvent evt) {
                    System.exit(0);
                }
            });        // 启动几个写操作线程向
            // System.out和System.err输出
            startWriterTestThread(
                "写操作线程 #1", System.err, 920, 50);
            startWriterTestThread(
                "写操作线程 #2", System.out, 500, 50);
            startWriterTestThread(
                "写操作线程 #3", System.out, 200, 50);
            startWriterTestThread(
                "写操作线程 #4", System.out, 1000, 50);
            startWriterTestThread(
                "写操作线程 #5", System.err, 850,    50);
        } // main()
        private static void startWriterTestThread(
            final String name, final PrintStream ps, 
            final int delay, final int count) {
            new Thread(new Runnable() {
                public void run() {
                    for(int i = 1; i <= count; ++i) {
                        ps.println("***" + name + ", hello !, i=" + i);
                        try {
                            Thread.sleep(delay);
                        }
                        catch(InterruptedException e) {}
                    }
                }
            }).start();
        } // startWriterTestThread()
    } // ConsoleTextArea main()方法创建了一个JFrame,JFrame包含一个ConsoleTextArea的实例。这些代码并没有什么特别之处。Frame显示出来之后,main()方法启动一系列的写操作线程,写操作线程向控制台流输出大量信息。ConsoleTextArea捕获并显示这些信息,如图一所示。ConsoleTextArea提供了两个构造函数。没有参数的构造函数用来捕获和显示所有写入到控制台流的数据,有一个InputStream[]参数的构造函数转发所有从各个数组元素读取的数据到JTextArea。稍后将有一个例子显示这个构造函数的用处。首先我们来看看没有参数的ConsoleTextArea构造函数。这个函数首先创建一个LoopedStreams对象;然后请求Java运行时环境把控制台输出转发到LoopedStreams提供的OutputStream;最后,构造函数调用startConsoleReaderThread(),创建一个不断地把文本行追加到JTextArea的线程。注意,把文本追加到JTextArea之后,程序小心地保证了插入点的正确位置。一般来说,Swing部件的更新不应该在AWT事件分派线程(AWT Event Dispatch Thread,AEDT)之外进行。对于本例来说,这意味着所有把文本追加到JTextArea的操作应该在AEDT中进行,而不是在startConsoleReaderThread()方法创建的线程中进行。然而,事实上在Swing中向JTextArea追加文本是一个线程安全的操作。读取一行文本之后,我们只需调用JText.append()就可以把文本追加到JTextArea的末尾。
    三、捕获其他程序的控制台输出
    在JTextArea中捕获Java程序自己的控制台输出是一回事,去捕获其他程序(甚至包括一些非Java程序)的控制台数据又是另一回事。ConsoleTextArea提供了捕获其他应用的输出时需要的基础功能,Listing 6的AppOutputCapture利用ConsoleTextArea,截取其他应用的输出信息然后显示在ConsoleTextArea中。【Listing 6:截获其他程序的控制台输出】
    import java.awt.*;
    import java.awt.event.*;
    import java.io.*;
    import javax.swing.*;public class AppOutputCapture {
            private static Process process;        public static void main(String[] args) {
                    if(args.length == 0) {
                 System.err.println("用法:java AppOutputCapture " +
                     "<程序名字> {参数1 参数2 ...}");
                 System.exit(0);
                    }                try {
                            // 启动命令行指定程序的新进程
                            process = Runtime.getRuntime().exec(args);
                    }
                    catch(IOException e) {
                            System.err.println("创建进程时出错...\n" + e);
                            System.exit(1);
                    }                // 获得新进程所写入的流
                    InputStream[] inStreams =
                            new InputStream[] {
                        process.getInputStream(),process.getErrorStream()};
                    ConsoleTextArea cta = new
        ConsoleTextArea(inStreams);
                    cta.setFont(java.awt.Font.decode("monospaced"));                JFrame frame = new JFrame(args[0] +
                            "控制台输出");                frame.getContentPane().add(new JScrollPane(cta),
                        BorderLayout.CENTER);
                    frame.setBounds(50, 50, 400, 400);
                    frame.setVisible(true);                frame.addWindowListener(new WindowAdapter() {
                            public void windowClosing(WindowEvent evt) {
                                    process.destroy();
                                    try {
                                            process.waitFor(); // 在Win98下可能被挂起
                                    }
                                    catch(InterruptedException e) {}
                                            System.exit(0);
                                    }
                            });
            } // main()
    } // AppOutputCapture
     AppOutputCapture的工作过程如下:首先利用Runtime.exec()方法启动指定程序的一个新进程。启动新进程之后,从结果Process对象得到它的控制台流。之后,把这些控制台流传入ConsoleTextArea(InputStream[])构造函数(这就是带参数ConsoleTextArea构造函数的用处)。使用AppOutputCapture时,在命令行上指定待截取其输出的程序名字。例如,如果在Windows 2000下执行javaw.exe AppOutputCapture ping.exe www.yahoo.com,则结果如图四所
      

  13.   

    To  jimjxr(宝宝猫)
    呵呵,是啊,服务器上保证有Servlet引擎--因为这是我自己用的,不是给用户用:)To zfluo(云淡风清) 
    谢谢:),我就是因为Ant看得不够深,现在能运行了,但最重要的是还没有截获输出。而且,我也不希望在服务器上架设更多的服务,如Telnet、Ftp,它们所能访问的只是服务器的部分资源,如果选用命令行,我就能得到全部的控制:)To turbochen(程序员) 
    呵呵,谢谢!我这就去试一试具体的实现:)谢谢大家的鼎力相助!
      

  14.   

    Sorry,好几天没上网,现在结贴
    用Runtime.exec解决了,但输出的字符总是很乱,不过并无大碍:)