
net.sf.eBus.client.EUDPServer Maven / Gradle / Ivy
//
// Copyright 2020 Charles W. Rapp
//
// 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 net.sf.eBus.client;
import com.google.common.base.Strings;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import static net.sf.eBus.client.EAbstractConnection.ANY_PORT;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.client.sysmessages.UdpConnectReply;
import net.sf.eBus.client.sysmessages.UdpConnectRequest;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EReplyMessage.ReplyStatus;
import net.sf.eBus.messages.ESystemMessage;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AbstractAsyncDatagramSocket;
import static net.sf.eBus.net.AsyncChannel.LOCALHOST;
import net.sf.eBus.net.AsyncDatagramSocket;
import net.sf.eBus.net.DatagramBufferWriter;
import net.sf.eBus.net.DatagramListener;
/**
* This UDP datagram socket "accepts" a new UDP datagram socket
* from a remote eBus application. While UDP is a connection-less
* protocol eBus is connection-based. So a UDP server socket is
* used to accept a new connection via when it receives a
* {@link UdpConnectRequest} message. UDP server is created in
* two ways: via the
* {@link net.sf.eBus.config.EConfigure.ServerBuilder} API and
* passing the {@link Service} instance to
* {@link EUDPServer#EUDPServer(EConfigure.Service)} or at
* application start using a properties or JSON configuration
* file. See {@link net.sf.eBus.config.EConfigure} about using
* properties or configuration files.
*
* @see ETCPServer
* @see EUDPConnection
*
* @author Charles W. Rapp
*/
public class EUDPServer
extends EServer
implements DatagramListener
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* UDP connect request key identifier.
*/
private static final int UDP_CONNECT_REQUEST_KEY_ID =
(SystemMessageType.UDP_CONNECT_REQUEST).keyId();
/**
* UDP connect reply key identifier.
*/
private static final int UDP_CONNECT_REPLY_KEY_ID =
(SystemMessageType.UDP_CONNECT_REPLY).keyId();
//-----------------------------------------------------------
// Statics.
//
/**
* Message type for de-serializing UDP connect request
* messages.
*/
private static final MessageType sRequestType =
(MessageType) DataType.findType(UdpConnectRequest.class);
private static final MessageType sReplyType =
(MessageType) DataType.findType(UdpConnectReply.class);
/**
* The logging subsystem interface.
*/
private static final Logger sLogger =
Logger.getLogger(EUDPServer.class.getName());
//-----------------------------------------------------------
// Locals.
//
/**
* Encodes a {@link UdpConnectReply} message to the UDP
* socket.
*/
private final MessageWriter mWriter;
/**
* UDP datagram socket used to accept UDP connect requests.
*/
private AsyncDatagramSocket mSocket;
/**
* The most recent UDP connect request came from this address.
* Stored temporarily during the connection process and
* dropped once the connect reply is sent.
*/
private SocketAddress mSourceAddress;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new, closed UDP server instance.
* @param config UDP server configuration.
*/
public EUDPServer(final Service config)
{
super (config);
mWriter = new MessageWriter();
} // end of EUDPServer(Service)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Implementations.
//
/**
* Creates a new {@code AsyncDatagramSocket} and opens on the
* given server port.
* @throws IOException
* if there is an I/O failure creating and opening the server
* UDP socket.
*/
@Override
protected void doOpen()
throws IOException
{
if (mSocket == null)
{
final AsyncDatagramSocket.DatagramBuilder builder =
AsyncDatagramSocket.builder();
mSocket =
builder.byteOrder(mConfiguration.byteOrder())
.inputBufferSize(mConfiguration.inputBufferSize())
.outputBufferSize(mConfiguration.outputBufferSize())
.selector(mConfiguration.serviceSelector())
.listener(this)
.build();
}
if (!mSocket.isOpen())
{
final SocketAddress bindAddr =
new InetSocketAddress(LOCALHOST, mPort);
mSocket.open(bindAddr);
}
} // end of doOpen()
/**
* Closes and drops the open UDP server socket.
*/
@Override
protected void doClose()
{
final AsyncDatagramSocket asyncSocket = mSocket;
if (asyncSocket != null && asyncSocket.isOpen())
{
mSocket = null;
asyncSocket.close();
}
} // end of doClose()
/**
* Closes the accepted UDP connection and sends a failed
* UDP connect reply.
* @param text explains the connect attempt failure.
* @param channel accepted UDP connection.
*/
@Override
protected void doAcceptFailed(final String text,
final SelectableChannel channel)
{
// Close the accepted channel first and then send the
// UDP connect reply via the server UDP socket.
try
{
channel.close();
}
catch (IOException ioex)
{
// Ignore exception when closing.
}
sendReply(ReplyStatus.ERROR, text, -1, mSourceAddress);
} // end of doAcceptFailed(String, SelectableChannel)
//
// end of Abstract Method Implementations.
//-----------------------------------------------------------
//-----------------------------------------------------------
// DatagramListener Interface Implementation.
//
/**
* Creates a {@link ERemoteApp} to handle this new
* connection if the connection passes the positive filter.
* If not, the accepted client connection is immediately
* closed.
* @param buffer contains encoded eBus message.
* @param address message received from this address.
* @param socket message received on this UDP socket.
*/
// java:S2095 - Resources should be closed.
// Since this is an async channel the underlying socket is
// closed at a later time.
@SuppressWarnings({"java:S2095"})
@Override
public void handleInput(final ByteBuffer buffer,
final InetSocketAddress address,
final AbstractAsyncDatagramSocket socket)
{
final UdpConnectRequest request = decode(buffer);
// Was a valid UDP connect request sent?
if (request != null)
{
// Yes. Create a UDP channel to go along with it.
try
{
final SocketAddress bindAddr =
new InetSocketAddress(ANY_PORT);
final DatagramChannel channel =
DatagramChannel.open(
StandardProtocolFamily.INET6);
// Have the datagram channel bind to any
// available ephemeral port.
channel.bind(bindAddr);
channel.configureBlocking(false);
if (sLogger.isLoggable(Level.FINE))
{
sLogger.fine(
String.format(
"UDP server %d: accepted connection from %s, new local UDP channel bound to %s.",
mPort,
address,
channel.getLocalAddress()));
}
mSourceAddress = address;
acceptConnection(address, channel);
}
catch (IOException ioex)
{
String message = ioex.getMessage();
if (Strings.isNullOrEmpty(message))
{
message = "(no reason given)";
}
if (sLogger.isLoggable(Level.WARNING))
{
sLogger.log(
Level.WARNING,
String.format(
"Failed to open DatagramChannel: %s",
message),
ioex);
}
sendReply(ReplyStatus.ERROR,
message,
-1,
address);
}
}
} // end of handleInput(...)
/**
* Logs the given {@code Throwable} as a warning.
* @param t error occurring on {@code socket}.
* @param socket UDP datagram socket on which the error
* occurred.
*/
@Override
public void handleError(final Throwable t,
final AbstractAsyncDatagramSocket socket)
{
sLogger.log(Level.WARNING,
String.format(
"%s: error on UDP server socket.",
socket.localSocketAddress()),
t);
} // end of handleError(...)
//
// end of DatagramListener Interface Implementation.
//-----------------------------------------------------------
/**
* Creates a {@link UdpConnectReply} from the parameters and
* sends that message to {@code address}.
* @param status connect attempt status.
* @param text text explaining {@code status}.
* @param address send the message to this address.
*/
/* package */ void sendReply(final ReplyStatus status,
final String text,
final int localPort,
final SocketAddress address)
{
final UdpConnectReply.Builder builder =
UdpConnectReply.builder();
final UdpConnectReply reply =
builder.status(status)
.text(text)
.port(localPort)
.build();
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"UDP Server %d: sending message to %s:%n%s",
mPort,
address,
reply));
}
mWriter.setWrite(reply, address);
try
{
mSocket.send(mWriter);
}
catch (IOException ioex)
{
sLogger.log(Level.WARNING,
String.format(
"UDP server %d: failed to send message:%n%s",
mPort,
reply),
ioex);
}
} // end of sendReply(...)
/**
* Returns the UDP connect request decoded from the given
* buffer. Returns {@code null} if the message is not a UDP
* connect request or the decode failed.
* @param buffer contains encoded eBus message.
* @param address message came from this remote eBus
* application.
* @return decoded eBus message.
* @throws BufferUnderflowException
* if {@code buffer} contains an incomplete UDP connect
* request message.
*/
private UdpConnectRequest decode(final ByteBuffer buffer)
{
final int size = buffer.getInt();
final int keyId = (size < 0 ? -1: buffer.getInt());
UdpConnectRequest retval = null;
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"%s: received message, size=%,d, key ID=%d.",
mSourceAddress,
size,
keyId));
}
// Ignore heartbeat messages and non-UDP connect request
// messages.
if (keyId == UDP_CONNECT_REQUEST_KEY_ID)
{
// Skip the from and to identifiers.
buffer.getInt();
buffer.getInt();
retval =
(UdpConnectRequest) sRequestType.deserialize(
ESystemMessage.SYSTEM_SUBJECT, buffer);
if (sLogger.isLoggable(Level.FINEST))
{
sLogger.finest(
String.format(
"%s: handling UDPConnectRequest message:%n%s",
mSocket.localSocketAddress(),
retval));
}
else if (sLogger.isLoggable(Level.FINER))
{
sLogger.finer(
String.format(
"%s: handling UDPConnectRequest message.",
mSocket.localSocketAddress()));
}
}
return (retval);
} // end of decode(ByteBuffer)
//---------------------------------------------------------------
// Member methods.
//
/**
* Used to encode a {@code UDPConnectReply} message directly
* to a datagram output buffer.
*/
private static final class MessageWriter
implements DatagramBufferWriter
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* UDP connect reply message to be serialized.
*/
private EMessage mMessage;
/**
* Contains the destination address of the next outbound
* UDP connect reply message. Set just prior to sending
* the message.
*/
private SocketAddress mDestination;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private MessageWriter()
{}
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// DatagramBufferWriter Interface Implementation.
//
/**
* Encodes {@link #mMessage} to {@code buffer} including
* the eBus message header.
* @param buffer encode the message to this buffer.
* @return the UDP packet destination.
*/
@Override
public SocketAddress fill(final ByteBuffer buffer)
{
// Skip over the size byte position and fill in last.
(buffer.position(EAbstractConnection.MESSAGE_SIZE_SIZE))
.putInt(UDP_CONNECT_REPLY_KEY_ID)
.putInt(EMessageHeader.NO_ID)
.putInt(EMessageHeader.NO_ID);
sReplyType.serialize(mMessage, buffer);
// Now write out the message size.
buffer.putInt(0, buffer.position());
return (mDestination);
} // end of fill(ByteBuffer)
//
// end of DatagramBufferWriter Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets the message and destination used by the
* {@code fill(ByteBuffer)} method.
* @param msg encode this message.
* @param destination send the encoded message to this
* destination.
*/
private void setWrite(final EMessage msg,
final SocketAddress destination)
{
mMessage = msg;
mDestination = destination;
} // end of setWrite(EMessage, SocketAddress)
//
// end of Set Methdos.
//-------------------------------------------------------
} // end of class MessageReader
} // end of class EUDPServer
© 2015 - 2025 Weber Informatics LLC | Privacy Policy