org.springframework.integration.ip.tcp.connection.TcpNioServerConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.ip.tcp.connection.TcpNioServerConnectionFactory.java

Source

/*
 * Copyright 2002-2019 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
 *
 *      https://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.io.UncheckedIOException;
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.lang.Nullable;
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 final Map<SocketChannel, TcpNioConnection> channelMap = new HashMap<>();

    private TcpNioConnectionSupport tcpNioConnectionSupport = new DefaultTcpNioConnectionSupport();

    private boolean multiAccept = true;

    private boolean usingDirectBuffers;

    private volatile ServerSocketChannel serverChannel;

    private volatile Selector selector;

    /**
     * Listens for incoming connections on the port.
     * @param port The port.
     */
    public TcpNioServerConnectionFactory(int port) {
        super(port);
    }

    /**
     * Set to false to only accept one connection per iteration over the
     * selector keys. This might be necessary to avoid accepts overwhelming
     * reads of existing sockets. By default when the {@code OP_ACCEPT} operation
     * is ready, we will keep accepting connections in a loop until no more arrive.
     * @param multiAccept false to accept connections one-at-a-time.
     * @since 5.1.4
     */
    public void setMultiAccept(boolean multiAccept) {
        this.multiAccept = multiAccept;
    }

    @Override
    public String getComponentType() {
        return "tcp-nio-server-connection-factory";
    }

    @Override
    public int getPort() {
        int port = super.getPort();
        ServerSocketChannel channel = this.serverChannel;
        if (port == 0 && channel != null) {
            try {
                SocketAddress address = channel.getLocalAddress();
                if (address instanceof InetSocketAddress) {
                    port = ((InetSocketAddress) address).getPort();
                }
            } catch (IOException e) {
                logger.error("Error getting port", e);
            }
        }
        return port;
    }

    @Override
    @Nullable
    public SocketAddress getServerSocketAddress() {
        if (this.serverChannel != null) {
            try {
                return this.serverChannel.getLocalAddress();
            } catch (IOException e) {
                logger.error("Error getting local address", 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 theSelector = Selector.open();
            if (this.serverChannel == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug(this + " stopped before registering the server channel");
                }
            } else {
                this.serverChannel.register(theSelector, SelectionKey.OP_ACCEPT);
                setListening(true);
                publishServerListeningEvent(getPort());
                this.selector = theSelector;
                doSelect(this.serverChannel, theSelector);
            }
        } 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 selectorToSelect the Selector multiplexor
     * @throws IOException
     */
    private void doSelect(ServerSocketChannel server, final Selector selectorToSelect) 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 = selectorToSelect.select(timeout);
                processNioSelections(selectionCount, selectorToSelect, server, this.channelMap);
            } catch (@SuppressWarnings("unused") CancelledKeyException cke) {
                logger.debug("CancelledKeyException during Selector.select()");
            } catch (ClosedSelectorException cse) {
                if (isActive()) {
                    logger.error("Selector closed", cse);
                    publishServerExceptionEvent(cse);
                    break;
                }
            }
        }
    }

    /**
     * @param selectorForNewSocket The selector.
     * @param server The server socket channel.
     * @param now The current time.
     */
    @Override
    protected void doAccept(final Selector selectorForNewSocket, ServerSocketChannel server, long now) {
        logger.debug("New accept");
        try {
            SocketChannel channel;
            do {
                channel = server.accept();
                if (channel != null) {
                    if (isShuttingDown()) {
                        if (logger.isInfoEnabled()) {
                            logger.info("New connection from " + channel.socket().getInetAddress().getHostAddress()
                                    + ":" + channel.socket().getPort()
                                    + " rejected; the server is in the process of shutting down.");
                        }
                        channel.close();
                    } else if (createConnectionForAcceptedChannel(selectorForNewSocket, now, channel) == null) {
                        return;
                    }
                }
            } while (this.multiAccept && channel != null);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Nullable
    private TcpNioConnection createConnectionForAcceptedChannel(Selector selectorForNewSocket, long now,
            SocketChannel channel) throws IOException {

        TcpNioConnection connection = null;
        try {
            channel.configureBlocking(false);
            Socket socket = channel.socket();
            setSocketAttributes(socket);
            connection = createTcpNioConnection(channel);
            if (connection != null) {
                connection.setTaskExecutor(getTaskExecutor());
                connection.setLastRead(now);
                Integer sslHandshakeTimeout = getSslHandshakeTimeout();
                if (sslHandshakeTimeout != null && connection instanceof TcpNioSSLConnection) {
                    ((TcpNioSSLConnection) connection).setHandshakeTimeout(sslHandshakeTimeout);
                }
                this.channelMap.put(channel, connection);
                channel.register(selectorForNewSocket, SelectionKey.OP_READ, connection);
                connection.publishConnectionOpenEvent();
            }
        } catch (IOException e) {
            logger.error("Exception accepting new connection from "
                    + channel.socket().getInetAddress().getHostAddress() + ":" + channel.socket().getPort(), e);
            channel.close();
        }
        return connection;
    }

    @Nullable
    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;
    }

}