/*
* Copyright 2002-2016 the original author or authors.
*
* 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.springframework.integration.ip.tcp.connection;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
/**
* Implements a server connection factory that produces {@link TcpNioConnection}s using
* a {@link ServerSocketChannel}. Must have a {@link TcpListener} registered.
*
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*
*/
public class TcpNioServerConnectionFactory extends AbstractServerConnectionFactory {
private volatile ServerSocketChannel serverChannel;
private volatile boolean usingDirectBuffers;
private final Map<SocketChannel, TcpNioConnection> channelMap = new HashMap<SocketChannel, TcpNioConnection>();
private volatile Selector selector;
private volatile TcpNioConnectionSupport tcpNioConnectionSupport = new DefaultTcpNioConnectionSupport();
/**
* Listens for incoming connections on the port.
* @param port The port.
*/
public TcpNioServerConnectionFactory(int port) {
super(port);
}
@Override
public String getComponentType() {
return "tcp-nio-server-connection-factory";
}
@Override
public int getPort() {
int port = super.getPort();
ServerSocketChannel serverChannel = this.serverChannel;
if (port == 0 && serverChannel != null) {
try {
SocketAddress address = serverChannel.getLocalAddress();
if (address instanceof InetSocketAddress) {
port = ((InetSocketAddress) address).getPort();
}
}
catch (IOException e) {
}
}
return port;
}
@Override
public SocketAddress getServerSocketAddress() {
if (this.serverChannel != null) {
try {
return this.serverChannel.getLocalAddress();
}
catch (IOException e) {
}
}
return null;
}
/**
* If no listener registers, exits.
* Accepts incoming connections and creates TcpConnections for each new connection.
* Invokes {{@link #initializeConnection(TcpConnectionSupport, Socket)} and executes the
* connection {@link TcpConnection#run()} using the task executor.
* I/O errors on the server socket/channel are logged and the factory is stopped.
*/
@Override
public void run() {
if (getListener() == null) {
logger.info(this + " No listener bound to server connection factory; will not read; exiting...");
return;
}
try {
this.serverChannel = ServerSocketChannel.open();
int port = super.getPort();
getTcpSocketSupport().postProcessServerSocket(this.serverChannel.socket());
this.serverChannel.configureBlocking(false);
if (getLocalAddress() == null) {
this.serverChannel.socket().bind(new InetSocketAddress(port), Math.abs(getBacklog()));
}
else {
InetAddress whichNic = InetAddress.getByName(getLocalAddress());
this.serverChannel.socket().bind(new InetSocketAddress(whichNic, port), Math.abs(getBacklog()));
}
if (logger.isInfoEnabled()) {
logger.info(this + " Listening");
}
final Selector selector = Selector.open();
if (this.serverChannel == null) {
if (logger.isDebugEnabled()) {
logger.debug(this + " stopped before registering the server channel");
}
}
else {
this.serverChannel.register(selector, SelectionKey.OP_ACCEPT);
setListening(true);
publishServerListeningEvent(getPort());
this.selector = selector;
doSelect(this.serverChannel, selector);
}
}
catch (IOException e) {
if (isActive()) {
logger.error("Error on ServerChannel; port = " + getPort(), e);
publishServerExceptionEvent(e);
}
stop();
}
finally {
setListening(false);
}
}
/**
* Listens for incoming connections and for notifications that a connected
* socket is ready for reading.
* Accepts incoming connections, registers the new socket with the
* selector for reading.
* When a socket is ready for reading, unregisters the read interest and
* schedules a call to doRead which reads all available data. When the read
* is complete, the socket is again registered for read interest.
* @param server the ServerSocketChannel to select
* @param selector the Selector multiplexor
* @throws IOException
*/
private void doSelect(ServerSocketChannel server, final Selector selector) throws IOException {
while (isActive()) {
int soTimeout = getSoTimeout();
int selectionCount = 0;
try {
long timeout = soTimeout < 0 ? 0 : soTimeout;
if (getDelayedReads().size() > 0 && (timeout == 0 || getReadDelay() < timeout)) {
timeout = getReadDelay();
}
if (logger.isTraceEnabled()) {
logger.trace("Delayed reads: " + getDelayedReads().size() + " timeout " + timeout);
}
selectionCount = selector.select(timeout);
processNioSelections(selectionCount, selector, server, this.channelMap);
}
catch (CancelledKeyException cke) {
logger.debug("CancelledKeyException during Selector.select()");
}
catch (ClosedSelectorException cse) {
if (isActive()) {
logger.error("Selector closed", cse);
publishServerExceptionEvent(cse);
break;
}
}
}
}
/**
* @param selector The selector.
* @param server The server socket channel.
* @param now The current time.
* @throws IOException Any IOException.
*/
@Override
protected void doAccept(final Selector selector, ServerSocketChannel server, long now) throws IOException {
logger.debug("New accept");
SocketChannel channel = server.accept();
if (isShuttingDown()) {
if (logger.isInfoEnabled()) {
logger.info("New connection from " + channel.socket().getInetAddress().getHostAddress()
+ " rejected; the server is in the process of shutting down.");
}
channel.close();
}
else {
try {
channel.configureBlocking(false);
Socket socket = channel.socket();
setSocketAttributes(socket);
TcpNioConnection connection = createTcpNioConnection(channel);
if (connection == null) {
return;
}
connection.setTaskExecutor(getTaskExecutor());
connection.setLastRead(now);
if (getSslHandshakeTimeout() != null && connection instanceof TcpNioSSLConnection) {
((TcpNioSSLConnection) connection).setHandshakeTimeout(getSslHandshakeTimeout());
}
this.channelMap.put(channel, connection);
channel.register(selector, SelectionKey.OP_READ, connection);
connection.publishConnectionOpenEvent();
}
catch (Exception e) {
logger.error("Exception accepting new connection", e);
channel.close();
}
}
}
private TcpNioConnection createTcpNioConnection(SocketChannel socketChannel) {
try {
TcpNioConnection connection = this.tcpNioConnectionSupport.createNewConnection(socketChannel, true,
isLookupHost(), getApplicationEventPublisher(), getComponentName());
connection.setUsingDirectBuffers(this.usingDirectBuffers);
TcpConnectionSupport wrappedConnection = wrapConnection(connection);
initializeConnection(wrappedConnection, socketChannel.socket());
return connection;
}
catch (Exception e) {
logger.error("Failed to establish new incoming connection", e);
return null;
}
}
@Override
public void stop() {
setActive(false);
if (this.selector != null) {
try {
this.selector.close();
}
catch (Exception e) {
logger.error("Error closing selector", e);
}
}
if (this.serverChannel != null) {
try {
this.serverChannel.close();
}
catch (IOException e) {
}
finally {
this.serverChannel = null;
}
}
super.stop();
}
public void setUsingDirectBuffers(boolean usingDirectBuffers) {
this.usingDirectBuffers = usingDirectBuffers;
}
public void setTcpNioConnectionSupport(TcpNioConnectionSupport tcpNioSupport) {
Assert.notNull(tcpNioSupport, "TcpNioSupport must not be null");
this.tcpNioConnectionSupport = tcpNioSupport;
}
/**
* @return the serverChannel
*/
protected ServerSocketChannel getServerChannel() {
return this.serverChannel;
}
/**
* @return the usingDirectBuffers
*/
protected boolean isUsingDirectBuffers() {
return this.usingDirectBuffers;
}
/**
* @return the connections
*/
protected Map<SocketChannel, TcpNioConnection> getConnections() {
return this.channelMap;
}
}