org.acplt.oncrpc.OncRpcTcpSocketHelper Maven / Gradle / Ivy
Show all versions of remotetea-oncrpc Show documentation
/*
* $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/OncRpcTcpSocketHelper.java,v 1.3 2007/05/29 19:45:46 haraldalbrecht Exp $
*
* Copyright (c) 2001
* Lehrstuhl fuer Prozessleittechnik (PLT), RWTH Aachen
* D-52064 Aachen, Germany.
* All rights reserved.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program (see the file LICENSE.txt for more
* details); if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.acplt.oncrpc;
import java.io.IOException;
import java.net.*;
import java.lang.reflect.*;
/**
* Wraps JRE-specific networking code for TCP/IP-based client sockets.
* So much for compile once, make it unuseable everywhere. Why could our
* great Sun simply not get their socket class straight from the beginning?
* The BSD socket API has been around since long enough and that real
* network applications need to control certain aspects of the transport
* layer's behaviour was also well known at this time -- looks like the
* one exceptions was -- and still is -- Sun, and the second one is MS.
*
* Sun always toutes Java as the perfect network "whatever" (replace with
* buzzword-of-the-day, like "programming language", "operating system",...)
* and especially their support for the Web. Sweet irony that it took them
* until JRE 1.3 to realize that half-closed connections are the way
* to do HTTP/1.0 non-persistent connections. And even more irony that
* they are now beginning to understand about polled network i/o.
*
*
The following JRE-dependent methods are wrapped and will just do
* nothing or return fake information on old JRE plattforms. The number
* after each method wrapper indicates the first JRE version supporting
* a particular feature:
*
* - setSendBufferSize() -- 1.2
* - setReceiveBufferSize() -- 1.2
*
*
* The following methods have been around since JDK 1.1, so we
* do not need to wrap them as we will never support JDK 1.0 -- let
* it rest in piece(s):
*
* - getTcpNoDelay() / setTcpNoDelay()
* - getSoTimeout() / setSoTimeout()
*
*
* In order to support connect() timeouts before JDK 1.4, there's
* now a connect()
method available. It is more than just a
* simple wrapper for pre JDK 1.4.
*
* @version $Revision: 1.3 $ $Date: 2007/05/29 19:45:46 $ $State: Exp $ $Locker: $
* @author Harald Albrecht
*/
public class OncRpcTcpSocketHelper {
/**
* Creates a stream socket helper and associates it with the given
* stream-based socket.
*
* @param socket The socket associated with this helper.
*/
public OncRpcTcpSocketHelper(Socket socket) {
this();
this.socket = socket;
}
/**
* Creates a stream socket helper but does not associates it with
* a real stream socket object. You need to call {@link #connect}
* lateron for a timeout-controlled connect.
*/
public OncRpcTcpSocketHelper() {
inspectSocketClassMethods();
}
/**
* Connects to specified TCP port at given host address, but aborts
* after timeout milliseconds if the connection could not be established
* within this time frame.
*
*
On pre-JRE 1.4 systems, this method will create a new thread
* to handle connection establishment. This should be no problem, as in
* general you might not want to connect to many ONC/RPC servers at the
* same time; but surely someone will soon pop up with a perfect
* reason just to do so...
*
* @param address The Java internet address representation of the remote
* host to connect to.
* @param port The port number at the remote host to connect to.
* @param timeout Timeout in milliseconds for connection operation.
* A negative timeout leaves the exact timeout up to the particular
* JVM and java.net implementation.
*
* @return A new socket instance connected to the specified host and port.
*
* @throws IOException with the message "connect interrupted" in case the
* timeout was reached before the connection could be established.
*/
public Socket connect(InetAddress address, int port, int timeout)
throws IOException {
if ( timeout < 0 ) {
//
// Leave the timeout up to the JVM and java.net implementation.
//
socket = new Socket(address, port);
return socket;
}
//
// Otherwise we have to implement a user-controlled timeout...
//
if ( methodConnect != null ) {
//
// Easy going. This JRE can do timeout-controlled connects
// itself. Fine. We just need to invoke the proper connect method.
// First, we call the Socket constructor without any parameters
// to create an unconnected socket (new in JRE 1.4), then we
// connect it to the destination -- well, at least we try to do so.
//
try {
socket = (Socket) ctor.newInstance(new Object [] {});
methodConnect.invoke(socket, new Object [] {
address,
new Integer(timeout)
});
} catch ( InvocationTargetException e ) {
Throwable t = e.getTargetException();
if ( t instanceof SocketException ) {
throw((SocketException) t);
} else if ( t instanceof IllegalArgumentException ) {
throw((IllegalArgumentException) t);
}
} catch ( IllegalAccessException e ) {
throw(new SocketException(e.getMessage()));
} catch ( InstantiationException e ) {
// This should never happen, but who knows...
throw(new SocketException(e.getMessage()));
}
} else {
//
// Sigh. We have to emulate timeout-controlled connects ourself.
// What a lousy JRE.
//
// Solution: we create a thread which will try to connect. It
// will signal us when it succeeded or some exception was thrown.
// We then wait for the signal for the given amount of time. In
// case our wait times out, we let the thread go...
//
Connectiator connectiator = new Connectiator(address, port);
connectiator.start();
synchronized ( connectiator ) {
try {
connectiator.wait(timeout);
} catch ( InterruptedException ie ) {
//
// Please note that we already reacquired the lock on the
// connectiator again, so we can signal it that we are
// not interested any more. However, this will not directly
// terminate it, as this is not possible.
//
connectiator.notRequiredAnyMore();
//
// Now inform the caller that something went awry.
//
throw(new IOException("connect interrupted"));
}
//
// Now find out whether the connection could be made within
// the time limit or not. We still need to hold the lock so
// we avoid race conditions when the connection thread just
// connects while we timed out.
//
// We either could connect successfully, or the connect operation
// threw an exception before we timed out. In case we could not
// connect because the socket constructor failed, we simply
// rethrow the exception.
//
IOException ie = connectiator.getIOException();
if ( ie != null ) {
throw(ie);
}
socket = connectiator.getSocket();
if ( socket == null ) {
//
// Same behaviour as constructor immediately trying to
// establish a connection.
//
throw(new NoRouteToHostException("Operation timed out: connect"));
}
// Well done. So to speak.
}
connectiator = null; // just to made the point...
}
return socket;
}
/**
* The class Connectiator
has a short and sometimes sad
* life, as its only purpose is trying to connect to a TCP port at
* another host machine.
*/
private class Connectiator extends Thread {
/**
* Construct a new Connectiator
that can later be used
* connect to the given TCP port at the host specified. Note that we
* do not try to establish the connection yet; this has to be done
* later using the {@link #run} method.
*
* @param address The Java internet address representation of the remote host to connect to.
* @param port The port number at the remote host to connect to.
*/
public Connectiator(InetAddress address, int port) {
this.address = address;
this.port = port;
}
public void run() {
//
// We need temporary object references here, as we are not allowed
// to assign to the object's member references "socket" and "ie"
// before we acquired the lock.
//
Socket mysocket = null;
IOException myie = null;
try {
mysocket = new Socket(address, port);
} catch ( IOException ie ) {
myie = ie;
}
//
// Acquire lock, so we can permanently set the connect's outcome
// and notify the waiting socket helper object that we did
// our job -- either good or not so good.
//
synchronized ( this ) {
socket = mysocket;
ioexception = myie;
this.notify();
if ( hitTheBucket && (socket != null) ) {
try {
socket.close();
} catch ( IOException e ) {
}
}
}
}
/**
* Return exception caused by connection operation, if any, or
* null
if no exception was thrown.
*
*
Note that we do not need to synchronize this method as the caller
* calls us when it is already holding the lock on us.
*
* @return Connection operation exception or null
.
*/
public IOException getIOException() {
return ioexception;
}
/**
* Return socket created by connection establishment, or
* null
if the connection could not be established.
*
*
Note that we do not need to synchronize this method as the caller
* calls us when it is already holding the lock on us.
*
* @return Socket or null
.
*/
public Socket getSocket() {
return socket;
}
/**
* Indicates that the caller initiating this Thread is not interested
* in its results any more.
*
*
Note that we do not need to synchronize this method as the caller
* calls us when it is already holding the lock on us.
*/
public void notRequiredAnyMore() {
hitTheBucket = true;
try {
this.interrupt();
} catch ( SecurityException e ) {
}
}
/**
* Host to connect to.
*/
private InetAddress address;
/**
* TCP port to connect to.
*/
private int port;
/**
* IOException
caused by connection attempt, if any,
* or null
.
*/
private IOException ioexception;
/**
* Socket object, if the connection could be established, or
* null
.
*/
private Socket socket;
/**
* Flag to indicate that the socket is not needed, as the caller
* timed out.
*/
private boolean hitTheBucket = false;
}
/**
* Sets the socket's send buffer size as a hint to the underlying
* transport layer to use appropriately sized I/O buffers. If the class
* libraries of the underlying JRE do not support setting the send
* buffer size, this is silently ignored.
*
* @param size The size to which to set the send buffer size. This value
* must be greater than 0.
*
* @throws SocketException if the socket's send buffer size could not
* be set, because the transport layer decided against accepting the
* new buffer size.
* @throws IllegalArgumentException if size
is 0 or negative.
*/
public void setSendBufferSize(int size)
throws SocketException {
if ( methodSetSendBufferSize != null ) {
try {
methodSetSendBufferSize.invoke(socket, new Object [] {
new Integer(size)
});
} catch ( InvocationTargetException e ) {
Throwable t = e.getTargetException();
if ( t instanceof SocketException ) {
throw((SocketException) t);
} else if ( t instanceof IllegalArgumentException ) {
throw((IllegalArgumentException) t);
}
} catch ( IllegalAccessException e ) {
throw(new SocketException(e.getMessage()));
}
}
}
/**
* Get size of send buffer for this socket.
*
* @return Size of send buffer.
*
* @throws SocketException If the transport layer could not be queried
* for the size of this socket's send buffer.
*/
public int getSendBufferSize()
throws SocketException {
if ( methodGetSendBufferSize != null ) {
try {
Object result = methodGetSendBufferSize.invoke(socket, (Object[]) null);
if ( result instanceof Integer ) {
return ((Integer) result).intValue();
}
} catch ( InvocationTargetException e ) {
Throwable t = e.getTargetException();
if ( t instanceof SocketException ) {
throw((SocketException) t);
}
throw(new SocketException(t.getMessage()));
} catch ( IllegalAccessException e ) {
throw(new SocketException(e.getMessage()));
}
}
//
// Without knowing better we can only return fake information.
// For quite some solaris OS revisions, the buffer size returned
// is beyond their typical configuration, with only 8k for fragment
// reassemble set asside by the IP layer. What a NOS...
//
return 65536;
}
/**
* Sets the socket's receive buffer size as a hint to the underlying
* transport layer to use appropriately sized I/O buffers. If the class
* libraries of the underlying JRE do not support setting the receive
* buffer size, this is silently ignored.
*
* @param size The size to which to set the receive buffer size. This value
* must be greater than 0.
*
* @throws SocketException if the socket's receive buffer size could not
* be set, because the transport layer decided against accepting the
* new buffer size.
* @throws IllegalArgumentException if size
is 0 or negative.
*/
public void setReceiveBufferSize(int size)
throws SocketException {
if ( methodSetSendBufferSize != null ) {
try {
methodSetReceiveBufferSize.invoke(socket, new Object [] {
new Integer(size)
});
} catch ( InvocationTargetException e ) {
Throwable t = e.getTargetException();
if ( t instanceof SocketException ) {
throw((SocketException) t);
} else if ( t instanceof IllegalArgumentException ) {
throw((IllegalArgumentException) t);
}
} catch ( IllegalAccessException e ) {
throw(new SocketException(e.getMessage()));
}
}
}
/**
* Get size of receive buffer for this socket.
*
* @return Size of receive buffer.
*
* @throws SocketException If the transport layer could not be queried
* for the size of this socket's receive buffer.
*/
public int getReceiveBufferSize()
throws SocketException {
if ( methodGetReceiveBufferSize != null ) {
try {
Object result = methodGetReceiveBufferSize.invoke(socket, (Object[]) null);
if ( result instanceof Integer ) {
return ((Integer) result).intValue();
}
} catch ( InvocationTargetException e ) {
Throwable t = e.getTargetException();
if ( t instanceof SocketException ) {
throw((SocketException) t);
}
throw(new SocketException(t.getMessage()));
} catch ( IllegalAccessException e ) {
throw(new SocketException(e.getMessage()));
}
}
//
// Without knowing better we can only return fake information.
// For quite some solaris OS revisions, the buffer size returned
// is beyond their typical configuration, with only 8k for fragment
// reassemble set asside by the IP layer. What a NOS...
//
return 65536;
}
/**
* Looks up methods of class Socket whether they are supported by the
* class libraries of the JRE we are currently executing on.
*/
protected void inspectSocketClassMethods() {
Class socketClass = Socket.class;
// JRE 1.2 specific stuff
try {
methodSetSendBufferSize = socketClass.getMethod("setSendBufferSize",
new Class [] {int.class});
methodGetSendBufferSize = socketClass.getMethod("getSendBufferSize", (Class[]) null);
} catch ( Exception e ) { }
try {
methodSetReceiveBufferSize = socketClass.getMethod("setReceiveBufferSize",
new Class [] {int.class});
methodGetReceiveBufferSize = socketClass.getMethod("getReceiveBufferSize", (Class[]) null);
} catch ( Exception e ) { }
// JRE 1.4 specific stuff
try {
ctor = socketClass.getConstructor(new Class [] {});
methodConnect = socketClass.getMethod("connect",
new Class [] {InetAddress.class, int.class});
} catch ( Exception e ) { }
}
/**
* The socket for which we have to help out with some missing methods.
*/
private Socket socket;
/**
* Method Socket.setSendBufferSize or null
if not available
* in the class library of a particular JRE.
*/
private Method methodSetSendBufferSize;
/**
* Method Socket.setReceiverBufferSize or null
if not available
* in the class library of a particular JRE.
*/
private Method methodSetReceiveBufferSize;
/**
* Method Socket.getSendBufferSize or null
if not available
* in the class library of a particular JRE.
*/
private Method methodGetSendBufferSize;
/**
* Method Socket.getReceiveBufferSize or null
if not available
* in the class library of a particular JRE.
*/
private Method methodGetReceiveBufferSize;
/**
* Method Socket.connect with timeout or null
if not available
* in the class library of a particular JRE.
*/
private Method methodConnect;
/**
* Constructor Socket() without any parameters or null
if not available
* in the class library of a particular JRE.
*/
private Constructor ctor;
}
// End of OncRpcTcpSocketHelper.java