dorkbox.network.Client Maven / Gradle / Ivy
/*
* Copyright 2010 dorkbox, llc
*
* Licensed 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 dorkbox.network;
import dorkbox.network.connection.BootstrapWrapper;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.EndPointClient;
import dorkbox.network.connection.idle.IdleBridge;
import dorkbox.network.connection.idle.IdleSender;
import dorkbox.network.connection.registration.local.RegistrationLocalHandlerClient;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerClientTCP;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerClientUDP;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerClientUDT;
import dorkbox.network.rmi.RemoteObject;
import dorkbox.network.rmi.TimeoutException;
import dorkbox.network.util.udt.UdtEndpointProxy;
import dorkbox.util.NamedThreadFactory;
import dorkbox.util.OS;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelOption;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.channel.socket.oio.OioSocketChannel;
import io.netty.util.internal.PlatformDependent;
import org.slf4j.Logger;
import java.io.IOException;
import java.net.InetSocketAddress;
/**
* The client is both SYNC and ASYNC. It starts off SYNC (blocks thread until it's done), then once it's connected to the server, it's
* ASYNC.
*/
@SuppressWarnings("unused")
public
class Client extends EndPointClient implements Connection {
/**
* Gets the version number.
*/
public static
String getVersion() {
return "1.20";
}
/**
* Starts a LOCAL only client, with the default local channel name and serialization scheme
*/
public
Client() throws InitializationException, SecurityException, IOException {
this(new Configuration(LOCAL_CHANNEL));
}
/**
* Starts a TCP & UDP client (or a LOCAL client), with the specified serialization scheme
*/
public
Client(String host, int tcpPort, int udpPort, int udtPort, String localChannelName)
throws InitializationException, SecurityException, IOException {
this(new Configuration(host, tcpPort, udpPort, udtPort, localChannelName));
}
/**
* Starts a REMOTE only client, which will connect to the specified host using the specified Connections Options
*/
@SuppressWarnings("AutoBoxing")
public
Client(final Configuration options) throws InitializationException, SecurityException, IOException {
super(options);
String threadName = Client.class.getSimpleName();
Logger logger2 = this.logger;
if (options.localChannelName != null && (options.tcpPort > 0 || options.udpPort > 0 || options.host != null) ||
options.localChannelName == null && (options.tcpPort == 0 || options.udpPort == 0 || options.host == null)) {
String msg = threadName + " Local channel use and TCP/UDP use are MUTUALLY exclusive. Unable to determine intent.";
logger2.error(msg);
throw new IllegalArgumentException(msg);
}
boolean isAndroid = PlatformDependent.isAndroid();
if (isAndroid && options.udtPort > 0) {
// Android does not support UDT.
if (logger2.isInfoEnabled()) {
logger2.info("Android does not support UDT.");
}
options.udtPort = -1;
}
final EventLoopGroup boss;
if (isAndroid) {
// android ONLY supports OIO (not NIO)
boss = new OioEventLoopGroup(0, new NamedThreadFactory(threadName, threadGroup));
}
else if (OS.isLinux()) {
// JNI network stack is MUCH faster (but only on linux)
boss = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup));
}
else {
boss = new NioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName, threadGroup));
}
manageForShutdown(boss);
if (options.localChannelName != null && options.tcpPort < 0 && options.udpPort < 0 && options.udtPort < 0) {
// no networked bootstraps. LOCAL connection only
Bootstrap localBootstrap = new Bootstrap();
this.bootstraps.add(new BootstrapWrapper("LOCAL", -1, localBootstrap));
EventLoopGroup localBoss = new DefaultEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(threadName + "-LOCAL",
threadGroup));
localBootstrap.group(localBoss)
.channel(LocalChannel.class)
.remoteAddress(new LocalAddress(options.localChannelName))
.handler(new RegistrationLocalHandlerClient(threadName, registrationWrapper));
manageForShutdown(localBoss);
}
else {
if (options.host == null) {
throw new IllegalArgumentException("You must define what host you want to connect to.");
}
if (options.tcpPort < 0 && options.udpPort < 0 && options.udtPort < 0) {
throw new IllegalArgumentException("You must define what port you want to connect to.");
}
if (options.tcpPort > 0) {
Bootstrap tcpBootstrap = new Bootstrap();
this.bootstraps.add(new BootstrapWrapper("TCP", options.tcpPort, tcpBootstrap));
if (isAndroid) {
// android ONLY supports OIO (not NIO)
tcpBootstrap.channel(OioSocketChannel.class);
}
else if (OS.isLinux()) {
// JNI network stack is MUCH faster (but only on linux)
tcpBootstrap.channel(EpollSocketChannel.class);
}
else {
tcpBootstrap.channel(NioSocketChannel.class);
}
tcpBootstrap.group(boss)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, WRITE_BUFF_HIGH)
.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, WRITE_BUFF_LOW)
.remoteAddress(options.host, options.tcpPort)
.handler(new RegistrationRemoteHandlerClientTCP(threadName,
registrationWrapper,
serializationManager));
// android screws up on this!!
tcpBootstrap.option(ChannelOption.TCP_NODELAY, !isAndroid)
.option(ChannelOption.SO_KEEPALIVE, true);
}
if (options.udpPort > 0) {
Bootstrap udpBootstrap = new Bootstrap();
this.bootstraps.add(new BootstrapWrapper("UDP", options.udpPort, udpBootstrap));
if (isAndroid) {
// android ONLY supports OIO (not NIO)
udpBootstrap.channel(OioDatagramChannel.class);
}
else if (OS.isLinux()) {
// JNI network stack is MUCH faster (but only on linux)
udpBootstrap.channel(EpollDatagramChannel.class);
}
else {
udpBootstrap.channel(NioDatagramChannel.class);
}
udpBootstrap.group(boss)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, WRITE_BUFF_HIGH)
.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, WRITE_BUFF_LOW)
.localAddress(new InetSocketAddress(0)) // bind to wildcard
.remoteAddress(new InetSocketAddress(options.host, options.udpPort))
.handler(new RegistrationRemoteHandlerClientUDP(threadName,
registrationWrapper,
serializationManager));
// Enable to READ and WRITE MULTICAST data (ie, 192.168.1.0)
// in order to WRITE: write as normal, just make sure it ends in .255
// in order to LISTEN:
// InetAddress group = InetAddress.getByName("203.0.113.0");
// NioDatagramChannel.joinGroup(group);
// THEN once done
// NioDatagramChannel.leaveGroup(group), close the socket
udpBootstrap.option(ChannelOption.SO_BROADCAST, false)
.option(ChannelOption.SO_SNDBUF, udpMaxSize);
}
if (options.udtPort > 0) {
// check to see if we have UDT available!
boolean udtAvailable = false;
try {
Class.forName("com.barchart.udt.nio.SelectorProviderUDT");
udtAvailable = true;
} catch (Throwable e) {
logger2.error("Requested a UDT connection on port {}, but the barchart UDT libraries are not loaded.", options.udtPort);
}
if (udtAvailable) {
// all of this must be proxied to another class, so THIS class doesn't have unmet dependencies.
Bootstrap udtBootstrap = new Bootstrap();
this.bootstraps.add(new BootstrapWrapper("UDT", options.udtPort, udtBootstrap));
EventLoopGroup udtBoss = UdtEndpointProxy.getWorker(DEFAULT_THREAD_POOL_SIZE, threadName, threadGroup);
UdtEndpointProxy.setChannelFactory(udtBootstrap);
udtBootstrap.group(udtBoss)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, WRITE_BUFF_HIGH)
.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, WRITE_BUFF_LOW)
.remoteAddress(options.host, options.udtPort)
.handler(new RegistrationRemoteHandlerClientUDT(threadName,
registrationWrapper,
serializationManager));
manageForShutdown(udtBoss);
}
}
}
}
/**
* Allows the client to reconnect to the last connected server
*
* @throws IOException
* if the client is unable to reconnect in the previously requested connection-timeout
*/
public
void reconnect() throws IOException {
reconnect(this.connectionTimeout);
}
/**
* Allows the client to reconnect to the last connected server
*
* @throws IOException
* if the client is unable to reconnect in the requested time
*/
public
void reconnect(int connectionTimeout) throws IOException {
// close out all old connections
closeConnections();
connect(connectionTimeout);
}
/**
* will attempt to connect to the server, with a 30 second timeout.
*
* @throws IOException
* if the client is unable to connect in 30 seconds
*/
public
void connect() throws IOException {
connect(30000);
}
/**
* will attempt to connect to the server, and will the specified timeout.
*
* will BLOCK until completed
*
* @param connectionTimeout
* wait for x milliseconds. 0 will wait indefinitely
*
* @throws IOException
* if the client is unable to connect in the requested time
*/
public
void connect(int connectionTimeout) throws IOException {
this.connectionTimeout = connectionTimeout;
// make sure we are not trying to connect during a close or stop event.
// This will wait until we have finished shutting down.
synchronized (this.shutdownInProgress) {
}
// have to start the registration process
this.connectingBootstrap.set(0);
registerNextProtocol();
// have to BLOCK
// don't want the client to run before registration is complete
synchronized (this.registrationLock) {
if (!registrationComplete) {
try {
this.registrationLock.wait(connectionTimeout);
} catch (InterruptedException e) {
throw new IOException("Unable to complete registration within '" + connectionTimeout + "' milliseconds", e);
}
}
}
connection = this.connectionManager.getConnection0();
}
@Override
public
boolean hasRemoteKeyChanged() {
return this.connection.hasRemoteKeyChanged();
}
/**
* @return the remote address, as a string.
*/
@Override
public
String getRemoteHost() {
return this.connection.getRemoteHost();
}
/**
* @return true if this connection is established on the loopback interface
*/
@Override
public
boolean isLoopback() {
return this.connection.isLoopback();
}
@SuppressWarnings("rawtypes")
@Override
public
EndPoint getEndPoint() {
return this;
}
/**
* @return the connection (TCP or LOCAL) id of this connection.
*/
@Override
public
int id() {
return this.connection.id();
}
/**
* @return the connection (TCP or LOCAL) id of this connection as a HEX string.
*/
@Override
public
String idAsHex() {
return this.connection.idAsHex();
}
@Override
public
boolean hasUDP() {
return this.connection.hasUDP();
}
@Override
public
boolean hasUDT() {
return this.connection.hasUDT();
}
/**
* Expose methods to send objects to a destination when the connection has become idle.
*/
@Override
public
IdleBridge sendOnIdle(IdleSender, ?> sender) {
return this.connection.sendOnIdle(sender);
}
/**
* Expose methods to send objects to a destination when the connection has become idle.
*/
@Override
public
IdleBridge sendOnIdle(Object message) {
return this.connection.sendOnIdle(message);
}
/**
* Marks the connection to be closed as soon as possible. This is evaluated when the current
* thread execution returns to the network stack.
*/
@Override
public
void closeAsap() {
this.connection.closeAsap();
}
/**
* Returns a new proxy object implements the specified interface. Methods invoked on the proxy object will be invoked remotely on the
* object with the specified ID in the ObjectSpace for the current connection.
*
* This will request a registration ID from the remote endpoint, and will block until the object has been returned.
*
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
* RemoteObject#setResponseTimeout(int) response timeout}.
*
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must not be
* called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a void return value can be
* called on the update thread.
*
* If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side will
* have the proxy object replaced with the registered (non-proxy) object.
*
* @see RemoteObject
*/
@Override
public
Iface createProxyObject(final Class remoteImplementationClass) throws IOException {
return this.connectionManager.getConnection0()
.createProxyObject(remoteImplementationClass);
}
/**
* Returns a new proxy object implements the specified interface. Methods invoked on the proxy object will be invoked remotely on the
* object with the specified ID in the ObjectSpace for the current connection.
*
* This will REUSE a registration ID from the remote endpoint, and will block until the object has been returned.
*
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
* RemoteObject#setResponseTimeout(int) response timeout}.
*
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must not be
* called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a void return value can be
* called on the update thread.
*
* If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side will
* have the proxy object replaced with the registered (non-proxy) object.
*
* @see RemoteObject
*/
@Override
public
Iface getProxyObject(final int objectId) throws IOException {
return this.connectionManager.getConnection0()
.getProxyObject(objectId);
}
/**
* Fetches the connection used by the client.
*
* Make sure that you only call this after the client connects!
*
* This is preferred to {@link EndPoint#getConnections()} getConnections()}, as it properly does some error checking
*/
public
C getConnection() {
return this.connection;
}
/**
* Closes all connections ONLY (keeps the client running). To STOP the client, use stop().
*
* This is used, for example, when reconnecting to a server.
*/
@Override
public
void close() {
closeConnections();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy