| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 2000,2001 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Xerces" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 1999, International |
| * Business Machines, Inc., http://www.apache.org. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package socket; |
| |
| import java.io.EOFException; |
| import java.io.FileInputStream; |
| import java.io.FilterInputStream; |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.util.Random; |
| |
| import socket.io.WrappedInputStream; |
| import socket.io.WrappedOutputStream; |
| |
| import org.apache.xerces.parsers.SAXParser; |
| |
| import org.xml.sax.AttributeList; |
| import org.xml.sax.DocumentHandler; |
| import org.xml.sax.HandlerBase; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.Parser; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| /** |
| * This sample provides a solution to the problem of 1) sending multiple |
| * XML documents over a single socket connection or 2) sending other types |
| * of data after the XML document without closing the socket connection. |
| * <p> |
| * The first situation is a problem because the XML specification does |
| * not allow a document to contain multiple root elements. Therefore a |
| * document stream must end (or at least appear to end) for the XML |
| * parser to accept it as the end of the document. |
| * <p> |
| * The second situation is a problem because the XML parser buffers the |
| * input stream in specified block sizes for performance reasons. This |
| * could cause the parser to accidentally read additional bytes of data |
| * beyond the end of the document. This actually relates to the first |
| * problem if the documents are encoding in two different international |
| * encodings. |
| * <p> |
| * The solution that this sample introduces wraps both the input and |
| * output stream on both ends of the socket. The stream wrappers |
| * introduce a protocol that allows arbitrary length data to be sent |
| * as separate, localized input streams. While the socket stream |
| * remains open, a separate input stream is created to "wrap" an |
| * incoming document and make it appear as if it were a standalone |
| * input stream. |
| * <p> |
| * To use this sample, enter any number of filenames of XML documents |
| * as parameters to the program. For example: |
| * <pre> |
| * java socket.KeepSocketOpen doc1.xml doc2.xml doc3.xml |
| * </pre> |
| * <p> |
| * This program will create a server and client thread that communicate |
| * on a specified port number on the "localhost" address. When the client |
| * connects to the server, the server sends each XML document specified |
| * on the command line to the client in sequence, wrapping each document |
| * in a WrappedOutputStream. The client uses a WrappedInputStream to |
| * read the data and pass it to the parser. |
| * <p> |
| * <strong>Note:</strong> Do not send any XML documents with associated |
| * grammars to the client. In other words, don't send any documents |
| * that contain a DOCTYPE line that references an external DTD because |
| * the client will not be able to resolve the location of the DTD and |
| * an error will be issued by the client. |
| * |
| * @see socket.io.WrappedInputStream |
| * @see socket.io.WrappedOutputStream |
| * |
| * @author Andy Clark, IBM |
| * |
| * @version $Id$ |
| */ |
| public class KeepSocketOpen { |
| |
| // |
| // MAIN |
| // |
| |
| /** Main program entry. */ |
| public static void main(String[] argv) throws Exception { |
| |
| // constants |
| final int port = 6789; |
| |
| // check args |
| if (argv.length == 0) { |
| System.out.println("usage: java socket.KeepSocketOpen file(s)"); |
| System.exit(1); |
| } |
| |
| // create server and client |
| Server server = new Server(port, argv); |
| Client client = new Client("localhost", port); |
| |
| // start it running |
| new Thread(server).start(); |
| new Thread(client).start(); |
| |
| } // main(String[]) |
| |
| // |
| // Classes |
| // |
| |
| /** |
| * Server. |
| * |
| * @author Andy Clark, IBM |
| */ |
| public static final class Server |
| extends ServerSocket |
| implements Runnable { |
| |
| // |
| // Data |
| // |
| |
| /** Files to send. */ |
| private String[] fFilenames; |
| |
| /** Verbose mode. */ |
| private boolean fVerbose; |
| |
| /** Buffer. */ |
| private byte[] fBuffer; |
| |
| // |
| // Constructors |
| // |
| |
| /** |
| * Constructs a server on the specified port and with the given |
| * file list in terse mode. |
| */ |
| public Server(int port, String[] filenames) throws IOException { |
| this(port, filenames, false); |
| } |
| |
| /** |
| * Constructs a server on the specified port and with the given |
| * file list and verbosity. |
| */ |
| public Server(int port, String[] filenames, boolean verbose) |
| throws IOException { |
| super(port); |
| System.out.println("Server: Created."); |
| fFilenames = filenames; |
| fVerbose = verbose; |
| //fBuffer = new byte[1024]; |
| fBuffer = new byte[4096<<2]; |
| } // <init>(int,String[]) |
| |
| // |
| // Runnable methods |
| // |
| |
| /** Runs the server. */ |
| public void run() { |
| |
| System.out.println("Server: Running."); |
| final Random random = new Random(System.currentTimeMillis()); |
| try { |
| |
| // accept connection |
| if (fVerbose) System.out.println("Server: Waiting for Client connection..."); |
| final Socket clientSocket = accept(); |
| final OutputStream clientStream = clientSocket.getOutputStream(); |
| System.out.println("Server: Client connected."); |
| |
| // send files, one at a time |
| for (int i = 0; i < fFilenames.length; i++) { |
| |
| // open file |
| String filename = fFilenames[i]; |
| System.out.println("Server: Opening file \""+filename+'"'); |
| FileInputStream fileIn = new FileInputStream(filename); |
| |
| // wrap stream |
| if (fVerbose) System.out.println("Server: Wrapping output stream."); |
| WrappedOutputStream wrappedOut = new WrappedOutputStream(clientStream); |
| |
| // read file, writing to output |
| int total = 0; |
| while (true) { |
| |
| // read random amount |
| //int length = (Math.abs(random.nextInt()) % fBuffer.length) + 1; |
| int length = fBuffer.length; |
| if (fVerbose) System.out.println("Server: Attempting to read "+length+" byte(s)."); |
| int count = fileIn.read(fBuffer, 0, length); |
| if (count == -1) { |
| if (fVerbose) System.out.println("Server: EOF."); |
| break; |
| } |
| if (fVerbose) System.out.println("Server: Writing "+count+" byte(s) to wrapped output stream."); |
| wrappedOut.write(fBuffer, 0, count); |
| total += count; |
| } |
| System.out.println("Server: Wrote "+total+" byte(s) total."); |
| |
| // close stream |
| if (fVerbose) System.out.println("Server: Closing output stream."); |
| wrappedOut.close(); |
| |
| // close file |
| if (fVerbose) System.out.println("Server: Closing file."); |
| fileIn.close(); |
| } |
| |
| // close connection to client |
| if (fVerbose) System.out.println("Server: Closing socket."); |
| clientSocket.close(); |
| |
| } |
| catch (IOException e) { |
| System.out.println("Server ERROR: "+e.getMessage()); |
| } |
| System.out.println("Server: Exiting."); |
| |
| } // run() |
| |
| } // class Server |
| |
| /** |
| * Client. |
| * |
| * @author Andy Clark, IBM |
| */ |
| public static final class Client |
| extends HandlerBase |
| implements Runnable { |
| |
| // |
| // Data |
| // |
| |
| /** Socket. */ |
| private Socket fServerSocket; |
| |
| /** Wrapped input stream. */ |
| private WrappedInputStream fWrappedInputStream; |
| |
| /** Verbose mode. */ |
| private boolean fVerbose; |
| |
| /** Buffer. */ |
| private byte[] fBuffer; |
| |
| /** Parser. */ |
| private SAXParser fParser; |
| |
| // parse data |
| |
| /** Number of elements. */ |
| private int fElementCount; |
| |
| /** Number of attributes. */ |
| private int fAttributeCount; |
| |
| /** Number of ignorable whitespace. */ |
| private int fIgnorableWhitespaceCount; |
| |
| /** Number of characters. */ |
| private int fCharactersCount; |
| |
| /** Time at start of parse. */ |
| private long fTimeBefore; |
| |
| // |
| // Constructors |
| // |
| |
| /** |
| * Constructs a Client that connects to the given port in terse |
| * output mode. |
| */ |
| public Client(String address, int port) throws IOException { |
| this(address, port, false); |
| fParser = new SAXParser(); |
| fParser.setDocumentHandler(this); |
| fParser.setErrorHandler(this); |
| } |
| |
| /** |
| * Constructs a Client that connects to the given address:port and |
| * with the specified verbosity. |
| */ |
| public Client(String address, int port, boolean verbose) |
| throws IOException { |
| System.out.println("Client: Created."); |
| fServerSocket = new Socket(address, port); |
| fVerbose = verbose; |
| fBuffer = new byte[1024]; |
| } // <init>(String,int) |
| |
| // |
| // Runnable methods |
| // |
| |
| /** Runs the client. */ |
| public void run() { |
| |
| System.out.println("Client: Running."); |
| try { |
| // get input stream |
| final InputStream serverStream = fServerSocket.getInputStream(); |
| |
| // read files from server |
| while (!Thread.interrupted()) { |
| // wrap input stream |
| if (fVerbose) System.out.println("Client: Wrapping input stream."); |
| fWrappedInputStream = new WrappedInputStream(serverStream); |
| InputStream in = new InputStreamReporter(fWrappedInputStream); |
| |
| // parse file |
| if (fVerbose) System.out.println("Client: Parsing XML document."); |
| InputSource source = new InputSource(in); |
| fParser.parse(source); |
| fWrappedInputStream = null; |
| |
| // close stream |
| if (fVerbose) System.out.println("Client: Closing input stream."); |
| in.close(); |
| |
| } |
| |
| // close socket |
| if (fVerbose) System.out.println("Client: Closing socket."); |
| fServerSocket.close(); |
| |
| } |
| catch (EOFException e) { |
| // server closed connection; ignore |
| } |
| catch (Exception e) { |
| System.out.println("Client ERROR: "+e.getMessage()); |
| } |
| System.out.println("Client: Exiting."); |
| |
| } // run() |
| |
| // |
| // DocumentHandler methods |
| // |
| |
| /** Start document. */ |
| public void startDocument() { |
| fElementCount = 0; |
| fAttributeCount = 0; |
| fIgnorableWhitespaceCount = 0; |
| fCharactersCount = 0; |
| fTimeBefore = System.currentTimeMillis(); |
| } // startDocument() |
| |
| /** Start element. */ |
| public void startElement(String name, AttributeList attrs) { |
| fElementCount++; |
| fAttributeCount += attrs != null ? attrs.getLength() : 0; |
| } // startElement(String,AttributeList) |
| |
| /** Ignorable whitespace. */ |
| public void ignorableWhitespace(char[] ch, int offset, int length) { |
| fIgnorableWhitespaceCount += length; |
| } // ignorableWhitespace(char[],int,int) |
| |
| /** Characters. */ |
| public void characters(char[] ch, int offset, int length) { |
| fCharactersCount += length; |
| } // characters(char[],int,int) |
| |
| /** End document. */ |
| public void endDocument() { |
| long timeAfter = System.currentTimeMillis(); |
| System.out.print("Client: "); |
| System.out.print(timeAfter - fTimeBefore); |
| System.out.print(" ms ("); |
| System.out.print(fElementCount); |
| System.out.print(" elems, "); |
| System.out.print(fAttributeCount); |
| System.out.print(" attrs, "); |
| System.out.print(fIgnorableWhitespaceCount); |
| System.out.print(" spaces, "); |
| System.out.print(fCharactersCount); |
| System.out.print(" chars)"); |
| System.out.println(); |
| } // endDocument() |
| |
| // |
| // ErrorHandler methods |
| // |
| |
| /** Warning. */ |
| public void warning(SAXParseException e) throws SAXException { |
| System.out.println("Client: [warning] "+e.getMessage()); |
| } // warning(SAXParseException) |
| |
| /** Error. */ |
| public void error(SAXParseException e) throws SAXException { |
| System.out.println("Client: [error] "+e.getMessage()); |
| } // error(SAXParseException) |
| |
| /** Fatal error. */ |
| public void fatalError(SAXParseException e) throws SAXException { |
| System.out.println("Client: [fatal error] "+e.getMessage()); |
| // on fatal error, skip to end of stream and end parse |
| try { |
| fWrappedInputStream.close(); |
| } |
| catch (IOException ioe) { |
| // ignore |
| } |
| throw e; |
| } // fatalError(SAXParseException) |
| |
| // |
| // Classes |
| // |
| |
| /** |
| * This class reports the actual number of bytes read at the |
| * end of "stream". |
| * |
| * @author Andy Clark, IBM |
| */ |
| class InputStreamReporter |
| extends FilterInputStream { |
| |
| // |
| // Data |
| // |
| |
| /** Total bytes read. */ |
| private long fTotal; |
| |
| // |
| // Constructors |
| // |
| |
| /** Constructs a reporter from the specified input stream. */ |
| public InputStreamReporter(InputStream stream) { |
| super(stream); |
| } // <init>(InputStream) |
| |
| // |
| // InputStream methods |
| // |
| |
| /** Reads a single byte. */ |
| public int read() throws IOException { |
| int b = super.in.read(); |
| if (b == -1) { |
| System.out.println("Client: Read "+fTotal+" byte(s) total."); |
| return -1; |
| } |
| fTotal++; |
| return b; |
| } // read():int |
| |
| /** Reads a block of bytes. */ |
| public int read(byte[] b, int offset, int length) |
| throws IOException { |
| int count = super.in.read(b, offset, length); |
| if (count == -1) { |
| System.out.println("Client: Read "+fTotal+" byte(s) total."); |
| return -1; |
| } |
| fTotal += count; |
| if (Client.this.fVerbose) System.out.println("Client: Actually read "+count+" byte(s)."); |
| return count; |
| } // read(byte[],int,int):int |
| |
| } // class InputStreamReporter |
| |
| } // class Client |
| |
| } // class KeepSocketOpen |