
org.jppf.nio.NioServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jppf-common Show documentation
Show all versions of jppf-common Show documentation
JPPF, the open source grid computing solution
/*
* JPPF.
* Copyright (C) 2005-2015 JPPF Team.
* http://www.jppf.org
*
* 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 org.jppf.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.*;
import org.jppf.io.IO;
import org.jppf.ssl.SSLHelper;
import org.jppf.utils.*;
import org.jppf.utils.streams.StreamUtils;
import org.slf4j.*;
/**
* Generic server for non-blocking asynchronous socket channel based communications.
* Instances of this class rely on a number of possible states for each socket channel,
* along with the possible transitions between thoses states.
* The design of this class enforces the use of typesafe enumerations for the states
* and transitions, so the developers must think ahead of how to implement their server
* as a state machine.
* @param the type of the states to use.
* @param the type of the transitions to use.
* @author Laurent Cohen
* @author Lane Schwartz (dynamically allocated server port)
*/
public abstract class NioServer, T extends Enum> extends Thread {
/**
* Logger for this class.
*/
private static Logger log = LoggerFactory.getLogger(NioServer.class);
/**
* Determines whether DEBUG logging level is enabled.
*/
private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
/**
* the selector of all socket channels open with providers or nodes.
*/
protected Selector selector;
/**
* Flag indicating that this socket server is closed.
*/
private AtomicBoolean stopped = new AtomicBoolean(false);
/**
* The ports this server is listening to.
*/
protected int[] ports = null;
/**
* The SSL ports this server is listening to.
*/
protected int[] sslPorts = null;
/**
* Timeout for the select() operations. A value of 0 means no timeout, i.e.
* the Selector.select()
will be invoked without parameters.
*/
protected long selectTimeout = 0L;
/**
* The factory for this server.
*/
protected NioServerFactory factory = null;
/**
* Lock used to synchronize selector operations.
*/
protected ReentrantLock lock = new ReentrantLock();
/**
* Performs all operations that relate to channel states.
*/
protected StateTransitionManager transitionManager = null;
/**
* Shutdown requested for this server
*/
protected final AtomicBoolean requestShutdown = new AtomicBoolean(false);
/**
* The SSL context associated with this server.
*/
protected SSLContext sslContext = null;
/**
* The channel identifier for channels handled by this server.
*/
protected final int identifier;
/**
* List of opened server socket channels.
*/
private List servers = new Vector<>();
/**
* Initialize this server with a specified port number and name.
* @param identifier the channel identifier for channels handled by this server.
* @param useSSL determines whether an SSLContext should be created for this server.
* @throws Exception if the underlying server socket can't be opened.
*/
protected NioServer(final int identifier, final boolean useSSL) throws Exception {
super(JPPFIdentifiers.serverName(identifier));
this.identifier = identifier;
selector = Selector.open();
factory = createFactory();
transitionManager = new StateTransitionManager<>(this);
if (useSSL) createSSLContext();
}
/**
* Initialize this server with a specified list of port numbers and name.
* @param ports the list of ports this server accepts connections from.
* @param sslPorts the list of SSL ports this server accepts connections from.
* @param identifier the channel identifier for channels handled by this server.
* performed sequentially or through the executor thread pool.
* @throws Exception if the underlying server socket can't be opened.
*/
public NioServer(final int[] ports, final int[] sslPorts, final int identifier) throws Exception {
this(identifier, false);
this.ports = ports;
this.sslPorts = sslPorts;
init();
}
/**
* Create the factory holding all the states and transition mappings.
* @return an NioServerFactory
instance.
*/
protected abstract NioServerFactory createFactory();
/**
* Initialize the underlying server sockets.
* @throws Exception if any error occurs while initializing the server sockets.
*/
protected final void init() throws Exception {
if ((ports != null) && (ports.length != 0)) init(ports, false);
if ((sslPorts != null) && (sslPorts.length != 0)) init(sslPorts, true);
}
/**
* Initialize the underlying server sockets for the spcified array of ports.
* @param portsToInit the array of ports to initiialize.
* @param ssl true
if the server sockets should be initialized with SSL enabled, false
otherwise.
* @throws Exception if any error occurs while initializing the server sockets.
*/
private void init(final int[] portsToInit, final Boolean ssl) throws Exception {
for (int i=0; i 0L;
while (!isStopped() && !externalStopCondition()) {
try {
lock.lock();
} finally {
lock.unlock();
}
int n = hasTimeout ? selector.select(selectTimeout) : selector.select();
//if (!isStopped() && (n > 0) && !externalStopCondition()) go(selector.selectedKeys());
if (n > 0) go(selector.selectedKeys());
}
} catch (Throwable t) {
log.error("error in selector loop for {} : {}", getClass().getSimpleName(), t);
} finally {
end();
}
}
/**
* Determine whether a stop condition external to this server has been reached.
* The default implementation always returns whether shutdown was requested.
* Subclasses may override this behavior.
* @return true if this server should be stopped, false otherwise.
*/
protected boolean externalStopCondition() {
return requestShutdown.get();
}
/**
* Initiates shutdown of this server.
*/
public void shutdown() {
requestShutdown.set(true);
try {
lock.lock();
selector.wakeup();
} finally {
lock.unlock();
}
}
/**
* Process the keys selected by the selector for IO operations.
* @param selectedKeys the set of keys that were selected by the latest select()
invocation.
* @throws Exception if an error is raised while processing the keys.
*/
public void go(final Set selectedKeys) throws Exception {
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
NioContext context = null;
try {
if (!key.isValid()) continue;
if (key.isAcceptable()) doAccept(key);
else {
context = (NioContext) key.attachment();
transitionManager.submitTransition(context.getChannel());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
if (context != null) context.handleException(context.getChannel(), e);
if (!(key.channel() instanceof ServerSocketChannel)) {
try {
key.channel().close();
} catch (Exception e2) {
log.error(e2.getMessage(), e2);
}
}
}
}
}
/**
* accept the incoming connection.
* It accept and put it in a state to define what type of peer is.
* @param key the selection key that represents the channel's registration with the selector.
*/
@SuppressWarnings("unchecked")
private void doAccept(final SelectionKey key) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
boolean ssl = (Boolean) key.attachment();
SocketChannel channel;
try {
channel = serverSocketChannel.accept();
} catch (IOException e) {
log.error(e.getMessage(), e);
return;
}
if (channel == null) return;
Runnable task = new AcceptChannelTask(channel, ssl);
transitionManager.submit(task);
//task.run();
}
/**
* Register an incoming connection with this server's selector.
* The channel is registered with an empty set of initial interest operations,
* which means a call to the corresponding {@link SelectionKey}'s interestOps()
method will return 0.
* @param channel the socket channel representing the connection.
* @param sslHandler an sslEngine eventually passed on from a different server.
* @param ssl specifies whether an SSLHandler
should be initialized for the channel.
* @return a wrapper for the newly registered channel.
*/
@SuppressWarnings("unchecked")
public ChannelWrapper> accept(final SocketChannel channel, final SSLHandler sslHandler, final boolean ssl) {
if (debugEnabled) log.debug("{} performing accept() of channel {}, ssl={}", new Object[] {this, channel, ssl});
NioContext context = createNioContext();
SelectionKeyWrapper wrapper = null;
lock.lock();
try {
if (sslHandler != null) context.setSSLHandler(sslHandler);
SelectionKey selKey = channel.register(selector.wakeup(), 0, context);
wrapper = new SelectionKeyWrapper(selKey);
context.setChannel(wrapper);
context.setSsl(ssl);
if (ssl && (sslHandler == null) && (sslContext != null)) {
if (debugEnabled) log.debug("creating SSLEngine for {}", wrapper);
SSLEngine engine = sslContext.createSSLEngine(channel.socket().getInetAddress().getHostAddress(), channel.socket().getPort());
configureSSLEngine(engine);
context.setSSLHandler(new SSLHandler(wrapper, engine));
}
postAccept(wrapper);
} catch (Exception e) {
wrapper = null;
log.error(e.getMessage(), e);
} finally {
lock.unlock();
}
return wrapper;
}
/**
* Process a channel that was accepted by the server socket channel.
* @param key the selection key for the socket channel to process.
*/
public abstract void postAccept(ChannelWrapper> key);
/**
* Define a context for a newly created channel.
* @return an NioContext
instance.
*/
public abstract NioContext> createNioContext();
/**
* Close the underlying server socket and stop this socket server.
*/
public void end() {
if (!isStopped()) {
if (debugEnabled) log.debug("closing server {}", this);
setStopped(true);
removeAllConnections();
}
}
/**
* Close and remove all connections accepted by this server.
*/
public void removeAllConnections() {
if (!isStopped()) return;
lock.lock();
try {
try {
selector.wakeup();
selector.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
for (ServerSocketChannel server: servers) {
try {
server.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
} finally {
lock.unlock();
}
}
/**
* Get all connections accepted by this server.
* @return a list of {@link ChannelWrapper} instances.
*/
public List> getAllConnections() {
List> channels = new ArrayList<>();
lock.lock();
try {
selector.wakeup();
Set keySet = selector.keys();
for (SelectionKey key: keySet) {
NioContext ctx = (NioContext) key.attachment();
channels.add(ctx.getChannel());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
lock.unlock();
}
return channels;
}
/**
* Get the selector for this server.
* @return a Selector instance.
*/
public Selector getSelector() {
return selector;
}
/**
* Get the factory for this server.
* @return an NioServerFactory
instance.
*/
public synchronized NioServerFactory getFactory() {
if (factory == null) factory = createFactory();
return factory;
}
/**
* Get the lock used to synchronize selector operations.
* @return a ReentrantLock
instance.
*/
public ReentrantLock getLock() {
return lock;
}
/**
* Set this server in the specified stopped state.
* @param stopped true if this server is stopped, false otherwise.
*/
protected void setStopped(final boolean stopped) {
this.stopped.set(stopped);
}
/**
* Get the stopped state of this server.
* @return true if this server is stopped, false otherwise.
*/
protected boolean isStopped() {
return stopped.get();
}
/**
* Get the manager that performs all operations that relate to channel states.
* @return a StateTransitionManager
instance.
*/
public StateTransitionManager getTransitionManager() {
return transitionManager;
}
/**
* Get the ports this server is listening to.
* @return an array of int values.
*/
public int[] getPorts() {
return ports;
}
/**
* Get the SSL ports this server is listening to.
* @return an array of int values.
*/
public int[] getSSLPorts() {
return sslPorts;
}
/**
* Get the SSL context associated with this server.
* @return a {@link SSLContext} instance.
*/
public SSLContext getSSLContext() {
return sslContext;
}
/**
* Determines whether the specified channel is in an idle state.
* @param channel the channel to check.
* @return true
if the channel is idle, false
otherwise.
*/
public abstract boolean isIdle(ChannelWrapper> channel);
/**
* This task performs the processing of a newly accepted channel.
*/
private class AcceptChannelTask implements Runnable {
/**
* The newly accepted socket channel.
*/
private final SocketChannel channel;
/**
* Determines whether ssl is enabled for the channel
*/
private final boolean ssl;
/**
* Initialize this task with the specified selection key.
* @param channel the newly accepted socket channel.
* @param ssl determines whether ssl is enabled for the channel.
*/
public AcceptChannelTask(final SocketChannel channel, final boolean ssl) {
this.channel = channel;
this.ssl = ssl;
}
@Override
public void run() {
try {
if (debugEnabled) log.debug("accepting channel {}, ssl={}", channel, ssl);
channel.socket().setSendBufferSize(IO.SOCKET_BUFFER_SIZE);
channel.socket().setReceiveBufferSize(IO.SOCKET_BUFFER_SIZE);
channel.socket().setTcpNoDelay(IO.SOCKET_TCP_NODELAY);
channel.socket().setKeepAlive(IO.SOCKET_KEEPALIVE);
if (channel.isBlocking()) channel.configureBlocking(false);
} catch (Exception e) {
log.error(e.getMessage(), e);
StreamUtils.close(channel, log);
return;
}
accept(channel, null, ssl);
}
}
/**
* Get the channel identifier for channels handled by this server.
* @return an int whose value is one of the constants defined in {@link org.jppf.utils.JPPFIdentifiers}.
*/
public int getIdentifier() {
return identifier;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy