/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package socket; import java.io.EOFException; import java.io.FileInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Random; import org.apache.xerces.parsers.SAXParser; import org.xml.sax.AttributeList; import org.xml.sax.HandlerBase; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import socket.io.WrappedInputStream; import socket.io.WrappedOutputStream; /** * 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. *

* 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. *

* 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. *

* 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. *

* To use this sample, enter any number of filenames of XML documents * as parameters to the program. For example: *

 * java socket.KeepSocketOpen doc1.xml doc2.xml doc3.xml
 * 
*

* 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. *

* Note: 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]; } // (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]; } // (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); } // (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