com.crankuptheamps.client.TCPTransportImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amps-client Show documentation
Show all versions of amps-client Show documentation
AMPS Java client by 60East Technologies, Inc.
///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2022 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties. This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights. This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.beans.ExceptionListener;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.net.SocketFactory;
import java.net.SocketTimeoutException;
import com.crankuptheamps.client.exception.AlreadyConnectedException;
import com.crankuptheamps.client.exception.ConnectionRefusedException;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.RetryOperationException;
import com.crankuptheamps.client.exception.InvalidURIException;
/**
* Socket implementation for {@link TCPTransport}. TCP Transport delegates to an instance of this class.
* This is used internally by the client's transport. There is usually no reason to make direct use of it.
* Its functionality is best accessed via the client instance.
*/
public class TCPTransportImpl
{
private URI _addr = null;
protected Socket _socket = null;
protected InputStream _inputStream = null;
protected OutputStream _outputStream = null;
public final Lock _lock = new ReentrantLock();
volatile int _connectionVersion = 0;
private volatile boolean _disconnecting = false;
private Protocol _messageType = null;
private MessageHandler _onMessage = DefaultMessageHandler.instance;
private TransportDisconnectHandler _onDisconnect = DefaultDisconnectHandler.instance;
private TCPReaderThread _readerThread = null;
private ExceptionListener _exceptionListener = null;
private Properties _properties = null;
protected TransportFilter _filter = null;
protected AMPSRunnable _idleRunnable = null;
private int _readTimeout = 0;
private int _connectTimeout = 0;
private ThreadCreatedHandler _threadCreatedHandler = null;
public TCPTransportImpl(Protocol messageType, Properties properties, TransportFilter filter)
{
this._messageType = messageType;
this._properties = properties;
this._filter = filter;
this._readTimeout = TCPTransport.getDefaultReadTimeout();
this._connectTimeout = TCPTransport.getDefaultConnectTimeout();
}
public void setMessageHandler(MessageHandler h)
{
this._onMessage = h;
}
public void setDisconnectHandler(TransportDisconnectHandler h)
{
this._onDisconnect = h;
}
public void setExceptionListener(ExceptionListener exceptionListener)
{
this._exceptionListener = exceptionListener;
}
public void setThreadCreatedHandler(ThreadCreatedHandler h)
{
this._threadCreatedHandler = h;
}
public void setTransportFilter(TransportFilter filter)
{
this._filter = filter;
}
public void connect(URI addr) throws ConnectionRefusedException,
AlreadyConnectedException, InvalidURIException
{
_lock.lock();
_disconnecting = false;
try
{
// clear out the interrupt bit. If we don't do this, and the thread is interrupted,
// EVERY SUBSEQUENT CALL TO connect() WILL FAIL FOREVER FROM THIS THREAD and throw a
// ClosedByInterruptException. Unlike InterruptedException, ClosedByInterruptException
// does not clear the thread's interrupted state.
Thread.interrupted();
if(this._addr != null)
{
throw new AlreadyConnectedException("Already connected to AMPS at " +
this._addr.getHost() + ":" + this._addr.getPort() + "\n");
}
_socket = createSocket();
// Merge properties from construction and the ones in this URI.
URIProperties properties = new URIProperties(addr);
if(_properties != null) properties.putAll(_properties);
applySocketProperties(properties);
String default_ip_proto_prefer = System.getProperty("com.crankuptheamps.client.DEFAULT_IP_PROTOCOL_PREFER", "ipv4");
if(!("ipv4".equals(default_ip_proto_prefer) || "ipv6".equals(default_ip_proto_prefer)))
{
throw new IllegalArgumentException("Invalid value '" + default_ip_proto_prefer + "' for com.crankuptheamps.client.DEFAULT_IP_PROTOCOL_PREFER System Property");
}
String ip_proto_prefer = properties.getProperty("ip_protocol_prefer", default_ip_proto_prefer);
if(!("ipv4".equals(ip_proto_prefer) || "ipv6".equals(ip_proto_prefer)))
{
throw new InvalidURIException("Invalid value '" + ip_proto_prefer + "' for ip_protocol_prefer URI parameter");
}
int preferredAddrIndex = -1;
InetAddress[] addrCandidates = InetAddress.getAllByName(addr.getHost());
if("ipv4".equals(ip_proto_prefer))
{
for(int i = 0; i < addrCandidates.length; i++)
{
if(addrCandidates[i] instanceof Inet4Address)
{
preferredAddrIndex = i;
break;
}
}
if(preferredAddrIndex == -1)
{
for(int i = 0; i < addrCandidates.length; i++)
{
if(addrCandidates[i] instanceof Inet6Address)
{
preferredAddrIndex = i;
break;
}
}
}
if(preferredAddrIndex == -1)
{
throw new UnknownHostException();
}
}
else
{
for(int i = 0; i < addrCandidates.length; i++)
{
if(addrCandidates[i] instanceof Inet6Address)
{
preferredAddrIndex = i;
break;
}
}
if(preferredAddrIndex == -1)
{
for(int i = 0; i < addrCandidates.length; i++)
{
if(addrCandidates[i] instanceof Inet4Address)
{
preferredAddrIndex = i;
break;
}
}
}
if(preferredAddrIndex == -1)
{
throw new UnknownHostException();
}
}
_socket.connect(new InetSocketAddress(addrCandidates[preferredAddrIndex], addr.getPort()));
handshake();
_inputStream = _socket.getInputStream();
_outputStream = _socket.getOutputStream();
final int INTERNAL_TIMEOUT = 1000; // 1 second
_socket.setSoTimeout(INTERNAL_TIMEOUT);
_connectionVersion++;
_readerThread = new TCPReaderThread(this, this._messageType);
_readerThread.barrier.await();
_addr = addr;
}
catch (InvalidURIException iuex)
{
throw iuex;
}
catch (ClosedByInterruptException e)
{
// Clear the interrupt flag! It is set, and simply catching this exception
// doesn't clear it.
Thread.interrupted();
throw new ConnectionRefusedException("Interrupted, but please try again.", e);
}
catch (BrokenBarrierException|InterruptedException e)
{
// Clear the interrupt flag! It is set, and simply catching this
// exception doesn't clear it.
Thread.interrupted();
throw new ConnectionRefusedException("Sync with reader thread interrupted, but please try again.", e);
}
catch (IllegalArgumentException iaex)
{
throw new InvalidURIException("Error setting socket options", iaex);
}
catch (Exception ex)
{
throw new ConnectionRefusedException("Unable to connect to AMPS at " +
addr.getHost() + ":" + addr.getPort(),
ex);
}
finally
{
_lock.unlock();
}
}
protected Socket createSocket() throws Exception
{
return SocketFactory.getDefault().createSocket();
}
protected void handshake() throws Exception
{
}
private void applySocketProperties(Properties properties_)
throws SocketException, InvalidURIException
{
_socket.setKeepAlive(true);
// We default SO_LINGER on, on Java, because
// a shutdown() option isn't available on Java SocketChannels until JDK 1.7.
_socket.setSoLinger(true, 10);
if(properties_ == null) return;
for(Map.Entry