| /* |
| * 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.Attributes; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| 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. |
| * <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 DefaultHandler |
| 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.setContentHandler(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() |
| |
| // |
| // ContentHandler methods |
| // |
| |
| /** Start document. */ |
| public void startDocument() { |
| fElementCount = 0; |
| fAttributeCount = 0; |
| fIgnorableWhitespaceCount = 0; |
| fCharactersCount = 0; |
| fTimeBefore = System.currentTimeMillis(); |
| } // startDocument() |
| |
| /** Start element. */ |
| public void startElement(String uri, String localName, String qName, Attributes atts) { |
| fElementCount++; |
| fAttributeCount += atts != null ? atts.getLength() : 0; |
| } // startElement(String,String,String,Attributes) |
| |
| /** Ignorable whitespace. */ |
| public void ignorableWhitespace(char[] ch, int start, int length) { |
| fIgnorableWhitespaceCount += length; |
| } // ignorableWhitespace(char[],int,int) |
| |
| /** Characters. */ |
| public void characters(char[] ch, int start, 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 |