
dorkbox.network.Client Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Network Show documentation
Show all versions of Network Show documentation
Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
/*
* 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.19";
}
/**
* 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