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