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

panda.net.SocketClient Maven / Gradle / Ivy

package panda.net;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

/**
 * The SocketClient provides the basic operations that are required of client objects accessing
 * sockets. It is meant to be subclassed to avoid having to rewrite the same code over and over
 * again to open a socket, close a socket, set timeouts, etc. Of special note is the
 * {@link #setSocketFactory setSocketFactory } method, which allows you to control the type of
 * Socket the SocketClient creates for initiating network connections. This is especially useful for
 * adding SSL or proxy support as well as better support for applets. For example, you could create
 * a {@link javax.net.SocketFactory} that requests browser security capabilities before creating a
 * socket. All classes derived from SocketClient should use the {@link #_socketFactory_
 * _socketFactory_ } member variable to create Socket and ServerSocket instances rather than
 * instantiating them by directly invoking a constructor. By honoring this contract you guarantee
 * that a user will always be able to provide his own Socket implementations by substituting his own
 * SocketFactory.
 * 
 * @see SocketFactory
 */
public abstract class SocketClient {
	/**
	 * The end of line character sequence used by most IETF protocols. That is a carriage return
	 * followed by a newline: "\r\n"
	 */
	public static final String NETASCII_EOL = "\r\n";

	/** The default SocketFactory shared by all SocketClient instances. */
	private static final SocketFactory __DEFAULT_SOCKET_FACTORY = SocketFactory.getDefault();

	/** The default {@link ServerSocketFactory} */
	private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY = ServerSocketFactory.getDefault();

	/**
	 * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners
	 * and the firing of ProtocolCommandEvents.
	 */
	private ProtocolCommandSupport __commandSupport;

	/** The timeout to use after opening a socket. */
	protected int _timeout_;

	/** The socket used for the connection. */
	protected Socket _socket_;

	/** The hostname used for the connection (null = no hostname supplied). */
	protected String _hostname_;

	/** The default port the client should connect to. */
	protected int _defaultPort_;

	/** The socket's InputStream. */
	protected InputStream _input_;

	/** The socket's OutputStream. */
	protected OutputStream _output_;

	/** The socket's SocketFactory. */
	protected SocketFactory _socketFactory_;

	/** The socket's ServerSocket Factory. */
	protected ServerSocketFactory _serverSocketFactory_;

	/** The socket's connect timeout (0 = infinite timeout) */
	private static final int DEFAULT_CONNECT_TIMEOUT = 0;
	protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;

	/** Hint for SO_RCVBUF size */
	private int receiveBufferSize = -1;

	/** Hint for SO_SNDBUF size */
	private int sendBufferSize = -1;

	/** The proxy to use when connecting. */
	private Proxy connProxy;

	/**
	 * Charset to use for byte IO.
	 */
	private Charset charset = Charset.defaultCharset();

	/**
	 * Default constructor for SocketClient. Initializes _socket_ to null, _timeout_ to 0,
	 * _defaultPort to 0, _isConnected_ to false, charset to {@code Charset.defaultCharset()} and
	 * _socketFactory_ to a shared instance of {@link panda.net.DefaultSocketFactory}.
	 */
	public SocketClient() {
		_socket_ = null;
		_hostname_ = null;
		_input_ = null;
		_output_ = null;
		_timeout_ = 0;
		_defaultPort_ = 0;
		_socketFactory_ = __DEFAULT_SOCKET_FACTORY;
		_serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
	}

	/**
	 * Because there are so many connect() methods, the _connectAction_() method is provided as a
	 * means of performing some action immediately after establishing a connection, rather than
	 * reimplementing all of the connect() methods. The last action performed by every connect()
	 * method after opening a socket is to call this method.
	 * 

* This method sets the timeout on the just opened socket to the default timeout set by * {@link #setDefaultTimeout setDefaultTimeout() }, sets _input_ and _output_ to the socket's * InputStream and OutputStream respectively, and sets _isConnected_ to true. *

* Subclasses overriding this method should start by calling * super._connectAction_() first to ensure the initialization of the * aforementioned protected variables. * * @throws IOException (SocketException) if a problem occurs with the socket */ protected void _connectAction_() throws IOException { _socket_.setSoTimeout(_timeout_); _input_ = _socket_.getInputStream(); _output_ = _socket_.getOutputStream(); } /** * Opens a Socket connected to a remote host at the specified port and originating from the * current host at a system assigned port. Before returning, {@link #_connectAction_ * _connectAction_() } is called to perform connection initialization actions. *

* * @param host The remote host. * @param port The port to connect to on the remote host. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. */ public void connect(InetAddress host, int port) throws SocketException, IOException { _hostname_ = null; _socket_ = _socketFactory_.createSocket(); if (receiveBufferSize != -1) { _socket_.setReceiveBufferSize(receiveBufferSize); } if (sendBufferSize != -1) { _socket_.setSendBufferSize(sendBufferSize); } _socket_.connect(new InetSocketAddress(host, port), connectTimeout); _connectAction_(); } /** * Opens a Socket connected to a remote host at the specified port and originating from the * current host at a system assigned port. Before returning, {@link #_connectAction_ * _connectAction_() } is called to perform connection initialization actions. *

* * @param hostname The name of the remote host. * @param port The port to connect to on the remote host. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. * @exception java.net.UnknownHostException If the hostname cannot be resolved. */ public void connect(String hostname, int port) throws SocketException, IOException { connect(InetAddress.getByName(hostname), port); _hostname_ = hostname; } /** * Opens a Socket connected to a remote host at the specified port and originating from the * specified local address and port. Before returning, {@link #_connectAction_ _connectAction_() * } is called to perform connection initialization actions. *

* * @param host The remote host. * @param port The port to connect to on the remote host. * @param localAddr The local address to use. * @param localPort The local port to use. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. */ public void connect(InetAddress host, int port, InetAddress localAddr, int localPort) throws SocketException, IOException { _hostname_ = null; _socket_ = _socketFactory_.createSocket(); if (receiveBufferSize != -1) { _socket_.setReceiveBufferSize(receiveBufferSize); } if (sendBufferSize != -1) { _socket_.setSendBufferSize(sendBufferSize); } _socket_.bind(new InetSocketAddress(localAddr, localPort)); _socket_.connect(new InetSocketAddress(host, port), connectTimeout); _connectAction_(); } /** * Opens a Socket connected to a remote host at the specified port and originating from the * specified local address and port. Before returning, {@link #_connectAction_ _connectAction_() * } is called to perform connection initialization actions. *

* * @param hostname The name of the remote host. * @param port The port to connect to on the remote host. * @param localAddr The local address to use. * @param localPort The local port to use. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. * @exception java.net.UnknownHostException If the hostname cannot be resolved. */ public void connect(String hostname, int port, InetAddress localAddr, int localPort) throws SocketException, IOException { connect(InetAddress.getByName(hostname), port, localAddr, localPort); _hostname_ = hostname; } /** * Opens a Socket connected to a remote host at the current default port and originating from * the current host at a system assigned port. Before returning, {@link #_connectAction_ * _connectAction_() } is called to perform connection initialization actions. *

* * @param host The remote host. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. */ public void connect(InetAddress host) throws SocketException, IOException { _hostname_ = null; connect(host, _defaultPort_); } /** * Opens a Socket connected to a remote host at the current default port and originating from * the current host at a system assigned port. Before returning, {@link #_connectAction_ * _connectAction_() } is called to perform connection initialization actions. *

* * @param hostname The name of the remote host. * @exception SocketException If the socket timeout could not be set. * @exception IOException If the socket could not be opened. In most cases you will only want to * catch IOException since SocketException is derived from it. * @exception java.net.UnknownHostException If the hostname cannot be resolved. */ public void connect(String hostname) throws SocketException, IOException { connect(hostname, _defaultPort_); _hostname_ = hostname; } /** * Disconnects the socket connection. You should call this method after you've finished using * the class instance and also before you call {@link #connect connect() } again. _isConnected_ * is set to false, _socket_ is set to null, _input_ is set to null, and _output_ is set to * null. *

* * @exception IOException If there is an error closing the socket. */ public void disconnect() throws IOException { closeQuietly(_socket_); closeQuietly(_input_); closeQuietly(_output_); _socket_ = null; _hostname_ = null; _input_ = null; _output_ = null; } private void closeQuietly(Socket socket) { if (socket != null) { try { socket.close(); } catch (IOException e) { // Ignored } } } private void closeQuietly(Closeable close) { if (close != null) { try { close.close(); } catch (IOException e) { // Ignored } } } /** * Returns true if the client is currently connected to a server. *

* Delegates to {@link Socket#isConnected()} * * @return True if the client is currently connected to a server, false otherwise. */ public boolean isConnected() { if (_socket_ == null) { return false; } return _socket_.isConnected(); } /** * Make various checks on the socket to test if it is available for use. Note that the only sure * test is to use it, but these checks may help in some cases. * * @return {@code true} if the socket appears to be available for use */ public boolean isAvailable() { if (isConnected()) { try { if (_socket_.getInetAddress() == null) { return false; } if (_socket_.getPort() == 0) { return false; } if (_socket_.getRemoteSocketAddress() == null) { return false; } if (_socket_.isClosed()) { return false; } /* * these aren't exact checks (a Socket can be half-open), but since we usually * require two-way data transfer, we check these here too: */ if (_socket_.isInputShutdown()) { return false; } if (_socket_.isOutputShutdown()) { return false; } /* ignore the result, catch exceptions: */ _socket_.getInputStream(); _socket_.getOutputStream(); } catch (IOException ioex) { return false; } return true; } else { return false; } } /** * Sets the default port the SocketClient should connect to when a port is not specified. The * {@link #_defaultPort_ _defaultPort_ } variable stores this value. If never set, the default * port is equal to zero. *

* * @param port The default port to set. */ public void setDefaultPort(int port) { _defaultPort_ = port; } /** * Returns the current value of the default port (stored in {@link #_defaultPort_ _defaultPort_ * }). *

* * @return The current value of the default port. */ public int getDefaultPort() { return _defaultPort_; } /** * Set the default timeout in milliseconds to use when opening a socket. This value is only used * previous to a call to {@link #connect connect()} and should not be confused with * {@link #setSoTimeout setSoTimeout()} which operates on an the currently opened socket. * _timeout_ contains the new timeout value. *

* * @param timeout The timeout in milliseconds to use for the socket connection. */ public void setDefaultTimeout(int timeout) { _timeout_ = timeout; } /** * Returns the default timeout in milliseconds that is used when opening a socket. *

* * @return The default timeout in milliseconds that is used when opening a socket. */ public int getDefaultTimeout() { return _timeout_; } /** * Set the timeout in milliseconds of a currently open connection. Only call this method after a * connection has been opened by {@link #connect connect()}. *

* To set the initial timeout, use {@link #setDefaultTimeout(int)} instead. * * @param timeout The timeout in milliseconds to use for the currently open socket connection. * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public void setSoTimeout(int timeout) throws SocketException { _socket_.setSoTimeout(timeout); } /** * Set the underlying socket send buffer size. *

* * @param size The size of the buffer in bytes. * @throws SocketException never thrown, but subclasses might want to do so */ public void setSendBufferSize(int size) throws SocketException { sendBufferSize = size; } /** * Get the current sendBuffer size * * @return the size, or -1 if not initialised */ protected int getSendBufferSize() { return sendBufferSize; } /** * Sets the underlying socket receive buffer size. *

* * @param size The size of the buffer in bytes. * @throws SocketException never (but subclasses may wish to do so) */ public void setReceiveBufferSize(int size) throws SocketException { receiveBufferSize = size; } /** * Get the current receivedBuffer size * * @return the size, or -1 if not initialised */ protected int getReceiveBufferSize() { return receiveBufferSize; } /** * Returns the timeout in milliseconds of the currently opened socket. *

* * @return The timeout in milliseconds of the currently opened socket. * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public int getSoTimeout() throws SocketException { return _socket_.getSoTimeout(); } /** * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the currently opened socket. *

* * @param on True if Nagle's algorithm is to be enabled, false if not. * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public void setTcpNoDelay(boolean on) throws SocketException { _socket_.setTcpNoDelay(on); } /** * Returns true if Nagle's algorithm is enabled on the currently opened socket. *

* * @return True if Nagle's algorithm is enabled on the currently opened socket, false otherwise. * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public boolean getTcpNoDelay() throws SocketException { return _socket_.getTcpNoDelay(); } /** * Sets the SO_KEEPALIVE flag on the currently opened socket. From the Javadocs, the default * keepalive time is 2 hours (although this is implementation dependent). It looks as though the * Windows WSA sockets implementation allows a specific keepalive value to be set, although this * seems not to be the case on other systems. * * @param keepAlive If true, keepAlive is turned on * @throws SocketException if there is a problem with the socket * @throws NullPointerException if the socket is not currently open */ public void setKeepAlive(boolean keepAlive) throws SocketException { _socket_.setKeepAlive(keepAlive); } /** * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket. Delegates * to {@link Socket#getKeepAlive()} * * @return True if SO_KEEPALIVE is enabled. * @throws SocketException if there is a problem with the socket * @throws NullPointerException if the socket is not currently open */ public boolean getKeepAlive() throws SocketException { return _socket_.getKeepAlive(); } /** * Sets the SO_LINGER timeout on the currently opened socket. *

* * @param on True if linger is to be enabled, false if not. * @param val The linger timeout (in hundredths of a second?) * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public void setSoLinger(boolean on, int val) throws SocketException { _socket_.setSoLinger(on, val); } /** * Returns the current SO_LINGER timeout of the currently opened socket. *

* * @return The current SO_LINGER timeout. If SO_LINGER is disabled returns -1. * @exception SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ public int getSoLinger() throws SocketException { return _socket_.getSoLinger(); } /** * Returns the port number of the open socket on the local host used for the connection. * Delegates to {@link Socket#getLocalPort()} *

* * @return The port number of the open socket on the local host used for the connection. * @throws NullPointerException if the socket is not currently open */ public int getLocalPort() { return _socket_.getLocalPort(); } /** * Returns the local address to which the client's socket is bound. Delegates to * {@link Socket#getLocalAddress()} *

* * @return The local address to which the client's socket is bound. * @throws NullPointerException if the socket is not currently open */ public InetAddress getLocalAddress() { return _socket_.getLocalAddress(); } /** * Returns the port number of the remote host to which the client is connected. Delegates to * {@link Socket#getPort()} *

* * @return The port number of the remote host to which the client is connected. * @throws NullPointerException if the socket is not currently open */ public int getRemotePort() { return _socket_.getPort(); } /** * @return The remote address to which the client is connected. Delegates to * {@link Socket#getInetAddress()} * @throws NullPointerException if the socket is not currently open */ public InetAddress getRemoteAddress() { return _socket_.getInetAddress(); } /** * Verifies that the remote end of the given socket is connected to the the same host that the * SocketClient is currently connected to. This is useful for doing a quick security check when * a client needs to accept a connection from a server, such as an FTP data connection or a BSD * R command standard error stream. *

* * @param socket the item to check against * @return True if the remote hosts are the same, false if not. */ public boolean verifyRemote(Socket socket) { InetAddress host1, host2; host1 = socket.getInetAddress(); host2 = getRemoteAddress(); return host1.equals(host2); } /** * Sets the SocketFactory used by the SocketClient to open socket connections. If the factory * value is null, then a default factory is used (only do this to reset the factory after having * previously altered it). Any proxy setting is discarded. *

* * @param factory The new SocketFactory the SocketClient should use. */ public void setSocketFactory(SocketFactory factory) { if (factory == null) { _socketFactory_ = __DEFAULT_SOCKET_FACTORY; } else { _socketFactory_ = factory; } // re-setting the socket factory makes the proxy setting useless, // so set the field to null so that getProxy() doesn't return a // Proxy that we're actually not using. connProxy = null; } /** * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket connections. If * the factory value is null, then a default factory is used (only do this to reset the factory * after having previously altered it). *

* * @param factory The new ServerSocketFactory the SocketClient should use. */ public void setServerSocketFactory(ServerSocketFactory factory) { if (factory == null) { _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY; } else { _serverSocketFactory_ = factory; } } /** * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} * object's connect() method. * * @param connectTimeout The connection timeout to use (in ms) */ public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; } /** * Get the underlying socket connection timeout. * * @return timeout (in ms) */ public int getConnectTimeout() { return connectTimeout; } /** * Get the underlying {@link ServerSocketFactory} * * @return The server socket factory */ public ServerSocketFactory getServerSocketFactory() { return _serverSocketFactory_; } /** * Adds a ProtocolCommandListener. * * @param listener The ProtocolCommandListener to add. */ public void addProtocolCommandListener(ProtocolCommandListener listener) { getCommandSupport().addProtocolCommandListener(listener); } /** * Removes a ProtocolCommandListener. * * @param listener The ProtocolCommandListener to remove. */ public void removeProtocolCommandListener(ProtocolCommandListener listener) { getCommandSupport().removeProtocolCommandListener(listener); } /** * If there are any listeners, send them the reply details. * * @param replyCode the code extracted from the reply * @param reply the full reply text */ protected void fireReplyReceived(int replyCode, String reply) { if (getCommandSupport().getListenerCount() > 0) { getCommandSupport().fireReplyReceived(replyCode, reply); } } /** * If there are any listeners, send them the command details. * * @param command the command name * @param message the complete message, including command name */ protected void fireCommandSent(String command, String message) { if (getCommandSupport().getListenerCount() > 0) { getCommandSupport().fireCommandSent(command, message); } } /** * Create the CommandSupport instance if required */ protected void createCommandSupport() { __commandSupport = new ProtocolCommandSupport(this); } /** * Subclasses can override this if they need to provide their own instance field for backwards * compatibilty. * * @return the CommandSupport instance, may be {@code null} */ protected ProtocolCommandSupport getCommandSupport() { return __commandSupport; } /** * Sets the proxy for use with all the connections. The proxy is used for connections * established after the call to this method. * * @param proxy the new proxy for connections. */ public void setProxy(Proxy proxy) { setSocketFactory(new DefaultSocketFactory(proxy)); connProxy = proxy; } /** * Gets the proxy for use with all the connections. * * @return the current proxy for connections. */ public Proxy getProxy() { return connProxy; } /** * Gets the charset name. * * @return the charset. */ public String getCharsetName() { return charset.name(); } /** * Gets the charset. * * @return the charset. */ public Charset getCharset() { return charset; } /** * Sets the charset. * * @param charset the charset. */ public void setCharset(Charset charset) { this.charset = charset; } /* * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility, so * the abstract method is needed to pass the instance to the methods which were moved here. */ }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy