All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.acplt.oncrpc.OncRpcUdpClient Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/OncRpcUdpClient.java,v 1.7 2009/02/25 19:20:40 haraldalbrecht Exp $
 *
 * Copyright (c) 1999, 2000
 * 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.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.DatagramSocket;

/**
 * ONC/RPC client which communicates with ONC/RPC servers over the network
 * using the datagram-oriented protocol UDP/IP.
 *
 * @version $Revision: 1.7 $ $Date: 2009/02/25 19:20:40 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
public class OncRpcUdpClient extends OncRpcClient {

    /**
     * Constructs a new OncRpcUdpClient object, which connects
     * to the ONC/RPC server at host for calling remote procedures
     * of the given { program, version }.
     *
     * 

Note that the construction of an OncRpcUdpProtocolClient * object will result in communication with the portmap process at * host if port is 0. * * @param host The host where the ONC/RPC server resides. * @param program Program number of the ONC/RPC server to call. * @param version Program version number. * @param port The port number where the ONC/RPC server can be contacted. * If 0, then the OncRpcUdpClient object will * ask the portmapper at host for the port number. * * @throws OncRpcException if an ONC/RPC error occurs. * @throws IOException if an I/O error occurs. */ public OncRpcUdpClient(InetAddress host, int program, int version, int port) throws OncRpcException, IOException { this(host, program, version, port, 8192); } /** * Constructs a new OncRpcUdpClient object, which connects * to the ONC/RPC server at host for calling remote procedures * of the given { program, version }. * *

Note that the construction of an OncRpcUdpProtocolClient * object will result in communication with the portmap process at * host if port is 0. * * @param host The host where the ONC/RPC server resides. * @param program Program number of the ONC/RPC server to call. * @param version Program version number. * @param port The port number where the ONC/RPC server can be contacted. * If 0, then the OncRpcUdpClient object will * ask the portmapper at host for the port number. * @param bufferSize The buffer size used for sending and receiving UDP * datagrams. * * @throws OncRpcException if an ONC/RPC error occurs. * @throws IOException if an I/O error occurs. */ public OncRpcUdpClient(InetAddress host, int program, int version, int port, int bufferSize) throws OncRpcException, IOException { // // Construct the inherited part of our object. This will also try to // lookup the port of the desired ONC/RPC server, if no port number // was specified (port = 0). // super(host, program, version, port, OncRpcProtocols.ONCRPC_UDP); // // Let the host operating system choose which port (and network // interface) to use. Then set the buffer sizes for sending and // receiving UDP datagrams. Finally set the destination of packets. // if ( bufferSize < 1024 ) { bufferSize = 1024; } socket = new DatagramSocket(); socketHelper = new OncRpcUdpSocketHelper(socket); if ( socketHelper.getSendBufferSize() < bufferSize ) { socketHelper.setSendBufferSize(bufferSize); } if ( socketHelper.getReceiveBufferSize() < bufferSize ) { socketHelper.setReceiveBufferSize(bufferSize); } // // Note: we don't do a // socket.connect(host, this.port); // here anymore. XdrUdpEncodingStream long since then supported // specifying the destination of an ONC/RPC UDP packet when // start serialization. In addition, connecting a UDP socket disables // the socket's ability to receive broadcasts. Without connecting you // can send an ONC/RPC call to the broadcast address of the network // and receive multiple replies. // // Create the necessary encoding and decoding streams, so we can // communicate at all. // sendingXdr = new XdrUdpEncodingStream(socket, bufferSize); receivingXdr = new XdrUdpDecodingStream(socket, bufferSize); } /** * Close the connection to an ONC/RPC server and free all network-related * resources. Well -- at least hope, that the Java VM will sometimes free * some resources. Sigh. * * @throws OncRpcException if an ONC/RPC error occurs. */ public void close() throws OncRpcException { if ( socket != null ) { socket.close(); socket = null; } if ( sendingXdr != null ) { try { sendingXdr.close(); } catch ( IOException e ) { } sendingXdr = null; } if ( receivingXdr != null ) { try { receivingXdr.close(); } catch ( IOException e ) { } receivingXdr = null; } } /** * Calls a remote procedure on an ONC/RPC server. * *

The OncRpcUdpClient uses a similar timeout scheme as * the genuine Sun C implementation of ONC/RPC: it starts with a timeout * of one second when waiting for a reply. If no reply is received within * this time frame, the client doubles the timeout, sends a new request * and then waits again for a reply. In every case the client will wait * no longer than the total timeout set through the * {@link #setTimeout(int)} method. * * @param procedureNumber Procedure number of the procedure to call. * @param versionNumber Protocol version number. * @param params The parameters of the procedure to call, contained * in an object which implements the {@link XdrAble} interface. * @param result The object receiving the result of the procedure call. * * @throws OncRpcException if an ONC/RPC error occurs. */ public synchronized void call(int procedureNumber, int versionNumber, XdrAble params, XdrAble result) throws OncRpcException { Refresh: for ( int refreshesLeft = 1; refreshesLeft >= 0; --refreshesLeft ) { // // First, build the ONC/RPC call header. Then put the sending // stream into a known state and encode the parameters to be // sent. Finally tell the encoding stream to send all its data // to the server. Then wait for an answer, receive it and decode // it. So that's the bottom line of what we do right here. // nextXid(); // // We only create our request message once and reuse it in case // retransmission should be necessary -- with the exception being // credential refresh. In this case we need to create a new // request message. // OncRpcClientCallMessage callHeader = new OncRpcClientCallMessage(xid, program, versionNumber, procedureNumber, auth); OncRpcClientReplyMessage replyHeader = new OncRpcClientReplyMessage(auth); long stopTime = System.currentTimeMillis() + timeout; int resendTimeout = retransmissionTimeout; // // Now enter the great loop where we send calls out to the server // and then sit there waiting for a reply. If none comes, we first // resend our call after one second, then two seconds, four seconds, // and so on, until we have reached the timeout for the call in total. // Note that this setting only applies if exponential back-off // retransmission has been selected. Per default we do not retransmit // any more, in order to be in line with the SUNRPC implementations. // do { try { // // Send call message to server. Remember that we've already // "connected" the datagram socket, so java.net knows whom // to send the datagram packets. // sendingXdr.beginEncoding(host, port); callHeader.xdrEncode(sendingXdr); params.xdrEncode(sendingXdr); sendingXdr.endEncoding(); } catch ( IOException e ) { throw(new OncRpcException(OncRpcException.RPC_CANTSEND, e.getLocalizedMessage())); } // // Receive reply message from server -- at least try to do so... // In case of batched calls we don't need no stinkin' answer, so // we can do other, more interesting things. // if ( timeout == 0 ) { return; } // // Wait for an answer to arrive... // for ( ;; ) { try { int currentTimeout = (int)(stopTime - System.currentTimeMillis()); if ( currentTimeout > resendTimeout ) { currentTimeout = resendTimeout; } else if ( currentTimeout < 1 ) { // // as setSoTimeout interprets a timeout of zero as // infinite (?�$@%&!!!) we need to ensure that we // have a finite timeout, albeit maybe an infinitesimal // finite one. currentTimeout = 1; } socket.setSoTimeout(currentTimeout); receivingXdr.beginDecoding(); // // FIX: do not check for reply address any longer as this // check causes problems with valid clustering usecases. // //# Only accept incomming reply if it comes from the same //# address we've sent the ONC/RPC call to. Otherwise throw //# away the datagram packet containing the reply and start //# over again, waiting for the next reply to arrive. // // if ( host.equals(receivingXdr.getSenderAddress()) ) { // // First, pull off the reply message header of the // XDR stream. In case we also received a verifier // from the server and this verifier was invalid, broken // or tampered with, we will get an // OncRpcAuthenticationException right here, which will // propagate up to the caller. If the server reported // an authentication problem itself, then this will // be handled as any other rejected ONC/RPC call. // try { replyHeader.xdrDecode(receivingXdr); } catch ( OncRpcException e ) { // // ** SF bug #1262106 ** // // We ran into some sort of trouble. Usually this will have // been a buffer underflow. Whatever, end the decoding process // and ensure this way that the next call has a chance to start // from a clean state. // receivingXdr.endDecoding(); throw(e); } // // Only deserialize the result, if the reply matches the call // and if the reply signals a successful call. In case of an // unsuccessful call (which mathes our call nevertheless) throw // an exception. // if ( replyHeader.messageId == callHeader.messageId ) { if ( !replyHeader.successfullyAccepted() ) { receivingXdr.endDecoding(); // // Check whether there was an authentication // problem. In this case first try to refresh the // credentials. // if ( (refreshesLeft > 0) && (replyHeader.replyStatus == OncRpcReplyStatus.ONCRPC_MSG_DENIED) && (replyHeader.rejectStatus == OncRpcRejectStatus.ONCRPC_AUTH_ERROR) && (auth != null) && auth.canRefreshCred() ) { // // Think about using a TAB size of four ;) // // Another instance of "CONTINUE considered // useful"... // continue Refresh; } // // Nope. No chance. This gets tough. // throw(replyHeader.newException()); } // // The reply header is okay and the call had been // accepted by the ONC/RPC server, so we can now // proceed to decode the outcome of the RPC. // try { result.xdrDecode(receivingXdr); } catch ( OncRpcException e ) { // // ** SF bug #1262106 ** // // We ran into some sort of trouble. Usually this will have // been a buffer underflow. Whatever, end the decoding process // and ensure this way that the next call has a chance to start // from a clean state. // receivingXdr.endDecoding(); throw(e); } // // Free pending resources of buffer and exit the call loop, // returning the reply to the caller through the result // object. // receivingXdr.endDecoding(); return; } else { // // The message id did no match -- probably some // old UDP datagram which just popped up from the // middle of the Internet. // // Yet another case of "CONTINUE considered not // harmful"... // // [Nothing to do here, just wait for the next datagram] } // } else { // // FIX: This part has become unnecessary as we // don't check the sender address for responses // any longer. // //# IP address of received UDP datagram is not the same //# as the IP address of the ONC/RPC server. //# //# [Nothing to do here, just wait for the next datagram] // } } catch ( InterruptedIOException e ) { // // Note that we only catch timeouts here, but no other // exceptions. Those others will go up further until someone // catches them. The timeouts are caught, so they can do no // damage but instead we start another round of sending a // request and waiting for a reply. Reminds me of NASA and // their "Mars Polar Lander"... // // Note that we need to leave the inner waiting loop here, // as we might need to resend the (lost) RPC request // datagram. // break; } catch ( IOException e ) { // // Argh. Trouble with the transport. Seems like we can't // receive data. Gosh. Go away! // try { receivingXdr.endDecoding(); // skip UDP record } catch ( IOException ioe ) { } throw(new OncRpcException(OncRpcException.RPC_CANTRECV, e.getLocalizedMessage())); } catch ( OncRpcException e ) { // // Ooops. An ONC/RPC exception. Let us rethrow this one, // as we won't have nothin' to do with it... // try { receivingXdr.endDecoding(); // skip UDP record } catch ( IOException ioe ) { } // // Well, in case we got not a *reply* RPC message back, // we keep listening for messages. // if ( e.getReason() != OncRpcException.RPC_WRONGMESSAGE ) { throw(e); } } // // We can not make use of the reply we just received, so // we need to dump it. // // This should raise no exceptions, when skipping the UDP // record. So if one is raised, we will rethrow an ONC/RPC // exception instead. // try { receivingXdr.endDecoding(); } catch ( IOException e ) { throw(new OncRpcException(OncRpcException.RPC_CANTRECV, e.getLocalizedMessage())); } } // // We only reach this code part beyond the inner waiting // loop if we run in a timeout and might need to retransmit // // According to the retransmission strategy choosen, update the // current retransmission (resending) timeout. // if ( retransmissionMode == OncRpcUdpRetransmissionMode.EXPONENTIAL ) { resendTimeout *= 2; } } while ( System.currentTimeMillis() < stopTime ); // // That's it -- this shity server does not talk to us. Now, due to // the indecent language used in the previous sentence, this software // can not be exported any longer to some countries of the world. // But this is surely not my problem, but rather theirs. So go away // and hide yourself in the dark with all your zombies (or maybe // kangaroos). // throw(new OncRpcTimeoutException()); } // for ( refreshesLeft ) } /** * Broadcast a remote procedure call to several ONC/RPC servers. For this * you'll need to specify either a multicast address or the subnet's * broadcast address when creating a OncRpcUdpClient. For * every reply received, an event containing the reply is sent to the * OncRpcBroadcastListener listener, which is the last * parameter to the this method. * *

In contrast to the {@link #call(int, XdrAble, XdrAble)} method, * broadcastCall will only send the ONC/RPC call once. It * will then wait for answers until the timeout as set by * {@link #setTimeout(int)} expires without resending the reply. * *

Note that you might experience unwanted results when using * authentication types other than {@link OncRpcClientAuthNone}, causing * messed up authentication protocol handling objects. This depends on * the type of authentication used. For AUTH_UNIX nothing * bad happens as long as none of the servers replies with a shorthand * verifier. If it does, then this shorthand will be used on all subsequent * ONC/RPC calls, something you probably do not want at all. * * @param procedureNumber Procedure number of the procedure to call. * @param params The parameters of the procedure to call, contained * in an object which implements the {@link XdrAble} interface. * @param result The object receiving the result of the procedure call. * Note that this object is reused to deserialize all incomming replies * one after another. * @param listener Listener which will get an {@link OncRpcBroadcastEvent} * for every reply received. * * @throws OncRpcException if an ONC/RPC error occurs. */ public synchronized void broadcastCall(int procedureNumber, XdrAble params, XdrAble result, OncRpcBroadcastListener listener) throws OncRpcException { // // First, build the ONC/RPC call header. Then put the sending // stream into a known state and encode the parameters to be // sent. Finally tell the encoding stream to broadcast all its data. // Then wait for answers, receive and decode them one at a time. // nextXid(); OncRpcClientCallMessage callHeader = new OncRpcClientCallMessage(xid, program, version, procedureNumber, auth); OncRpcClientReplyMessage replyHeader = new OncRpcClientReplyMessage(auth); // // Broadcast the call. Note that we send the call only once and will // never resend it. // try { // // Send call message to server. Remember that we've already // "connected" the datagram socket, so java.net knows whom // to send the datagram packets. // sendingXdr.beginEncoding(host, port); callHeader.xdrEncode(sendingXdr); params.xdrEncode(sendingXdr); sendingXdr.endEncoding(); } catch ( IOException e ) { throw(new OncRpcException(OncRpcException.RPC_CANTSEND, e.getLocalizedMessage())); } // // Now enter the great loop where sit waiting for replies to our // broadcast call to come in. In every case, we wait until the // (total) timeout expires. // long stopTime = System.currentTimeMillis() + timeout; do { try { // // Calculate timeout until the total timeout is reached, so // we can try to meet the overall deadline. // int currentTimeout = (int)(stopTime - System.currentTimeMillis()); if ( currentTimeout < 0 ) { currentTimeout = 0; } socket.setSoTimeout(currentTimeout); // // Then wait for datagrams to arrive... // receivingXdr.beginDecoding(); replyHeader.xdrDecode(receivingXdr); // // Only deserialize the result, if the reply matches the call // and if the reply signals a successful call. In case of an // unsuccessful call (which mathes our call nevertheless) throw // an exception. // if ( replyHeader.messageId == callHeader.messageId ) { if ( !replyHeader.successfullyAccepted() ) { // // We got a notification of a rejected call. We silently // ignore such replies and continue listening for other // replies. // receivingXdr.endDecoding(); /* fall through to time check */ } result.xdrDecode(receivingXdr); // // Notify a potential listener of the reply. // if ( listener != null ) { OncRpcBroadcastEvent evt = new OncRpcBroadcastEvent( this, receivingXdr.getSenderAddress(), procedureNumber, params, result); listener.replyReceived(evt); } // // Free pending resources of buffer and exit the call loop, // returning the reply to the caller through the result // object. // receivingXdr.endDecoding(); /* fall through to time check */ } else { // // This should raise no exceptions, when skipping the UDP // record. So if one is raised, we will rethrow an ONC/RPC // exception instead. // try { receivingXdr.endDecoding(); } catch ( IOException e ) { throw(new OncRpcException(OncRpcException.RPC_CANTRECV, e.getLocalizedMessage())); } /* fall through to time check */ } } catch ( InterruptedIOException e ) { // // Note that we only catch timeouts here, but no other // exceptions. Those others will go up further until someone // catches them. If we get the timeout we know that it // could be time to leave the stage and so we fall through // to the total timeout check. // /* fall through to time check */ } catch ( IOException e ) { // // Argh. Trouble with the transport. Seems like we can't // receive data. Gosh. Go away! // throw(new OncRpcException(OncRpcException.RPC_CANTRECV, e.getLocalizedMessage())); } } while ( System.currentTimeMillis() < stopTime ); return; } /** * Set the {@link OncRpcUdpRetransmissionMode retransmission mode} for * lost remote procedure calls. The default retransmission mode is * {@link OncRpcUdpRetransmissionMode#FIXED}. * * @param mode Retransmission mode (either fixed or exponential). */ public void setRetransmissionMode(int mode) { retransmissionMode = mode; } /** * Retrieve the current * {@link OncRpcUdpRetransmissionMode retransmission mode} set for * retransmission of lost ONC/RPC calls. * * @return Current retransmission mode. */ public int getRetransmissionMode() { return retransmissionMode; } /** * Set the retransmission timout for remote procedure calls to wait for * an answer from the ONC/RPC server before resending the call. The * default retransmission timeout is the 30 seconds. The retransmission * timeout must be > 0. To disable retransmission of lost calls, set * the retransmission timeout to be the same value as the timeout. * * @param milliseconds Timeout in milliseconds. A timeout of zero indicates * batched calls. */ public void setRetransmissionTimeout(int milliseconds) { if ( milliseconds <= 0 ) { throw(new IllegalArgumentException("timeouts must be positive.")); } retransmissionTimeout = milliseconds; } /** * Retrieve the current retransmission timeout set for remote procedure * calls. * * @return Current retransmission timeout. */ public int getRetransmissionTimeout() { return retransmissionTimeout; } /** * Set the character encoding for (de-)serializing strings. * * @param characterEncoding the encoding to use for (de-)serializing strings. * If null, the system's default encoding is to be used. */ public void setCharacterEncoding(String characterEncoding) { receivingXdr.setCharacterEncoding(characterEncoding); sendingXdr.setCharacterEncoding(characterEncoding); } /** * Get the character encoding for (de-)serializing strings. * * @return the encoding currently used for (de-)serializing strings. * If null, then the system's default encoding is used. */ public String getCharacterEncoding() { return receivingXdr.getCharacterEncoding(); } /** * UDP socket used for datagram-based communication with an ONC/RPC * server. */ private DatagramSocket socket; /** * Socket helper object supplying missing methods for JDK 1.1 * backwards compatibility. So much for compile once, does not run * everywhere. */ private OncRpcUdpSocketHelper socketHelper; /** * XDR encoding stream used for sending requests via UDP/IP to an ONC/RPC * server. */ protected XdrUdpEncodingStream sendingXdr; /** * XDR decoding stream used when receiving replies via UDP/IP from an * ONC/RPC server. */ protected XdrUdpDecodingStream receivingXdr; /** * Retransmission timeout used for resending ONC/RPC calls when an ONC/RPC * server does not answer fast enough. The default retransmission timeout * is identical to the overall timeout for ONC/RPC calls (thus UDP/IP-based * clients will not retransmit lost calls). * * @see OncRpcUdpClient#retransmissionMode * @see OncRpcClient#setTimeout */ protected int retransmissionTimeout = super.timeout; /** * Retransmission mode used when resending ONC/RPC calls. Default mode is * {@link OncRpcUdpRetransmissionMode#FIXED fixed timeout mode}. * * @see OncRpcUdpRetransmissionMode */ protected int retransmissionMode = OncRpcUdpRetransmissionMode.FIXED; } // End of OncRpcUdpClient.java





© 2015 - 2024 Weber Informatics LLC | Privacy Policy