/* * 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.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import org.springframework.scheduling.SchedulingAwareRunnable; import org.springframework.util.Assert; /** * A client connection factory that creates {@link TcpNioConnection}s. * @author Gary Russell * @author Artem Bilan * @since 2.0 * */ public class TcpNioClientConnectionFactory extends AbstractClientConnectionFactory implements SchedulingAwareRunnable { private volatile boolean usingDirectBuffers; private volatile Selector selector; private final Map<SocketChannel, TcpNioConnection> channelMap = new ConcurrentHashMap<SocketChannel, TcpNioConnection>(); private final BlockingQueue<SocketChannel> newChannels = new LinkedBlockingQueue<SocketChannel>(); private volatile TcpNioConnectionSupport tcpNioConnectionSupport = new DefaultTcpNioConnectionSupport(); /** * Creates a TcpNioClientConnectionFactory for connections to the host and port. * @param host the host * @param port the port */ public TcpNioClientConnectionFactory(String host, int port) { super(host, port); } @Override protected void checkActive() throws IOException { super.checkActive(); int n = 0; while (this.selector == null) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } if (n++ > 600) { throw new IOException("Factory failed to start"); } } } @Override protected TcpConnectionSupport buildNewConnection() throws Exception { SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(this.getHost(), this.getPort())); setSocketAttributes(socketChannel.socket()); TcpNioConnection connection = this.tcpNioConnectionSupport.createNewConnection( socketChannel, false, this.isLookupHost(), this.getApplicationEventPublisher(), this.getComponentName()); connection.setUsingDirectBuffers(this.usingDirectBuffers); connection.setTaskExecutor(this.getTaskExecutor()); if (getSslHandshakeTimeout() != null && connection instanceof TcpNioSSLConnection) { ((TcpNioSSLConnection) connection).setHandshakeTimeout(getSslHandshakeTimeout()); } TcpConnectionSupport wrappedConnection = wrapConnection(connection); initializeConnection(wrappedConnection, socketChannel.socket()); socketChannel.configureBlocking(false); if (this.getSoTimeout() > 0) { connection.setLastRead(System.currentTimeMillis()); } this.channelMap.put(socketChannel, connection); this.newChannels.add(socketChannel); this.selector.wakeup(); return wrappedConnection; } /** * When set to true, connections created by this factory attempt * to use direct buffers where possible. * @param usingDirectBuffers The usingDirectBuffers to set. * @see ByteBuffer */ public void setUsingDirectBuffers(boolean usingDirectBuffers) { this.usingDirectBuffers = usingDirectBuffers; } public void setTcpNioConnectionSupport(TcpNioConnectionSupport tcpNioSupport) { Assert.notNull(tcpNioSupport, "TcpNioSupport must not be null"); this.tcpNioConnectionSupport = tcpNioSupport; } @Override public boolean isLongLived() { return true; } @Override public void stop() { if (this.selector != null) { try { this.selector.close(); } catch (Exception e) { logger.error("Error closing selector", e); } } super.stop(); } @Override public void start() { synchronized (this.lifecycleMonitor) { if (!this.isActive()) { this.setActive(true); this.getTaskExecutor().execute(this); } } super.start(); } @Override public void run() { if (logger.isDebugEnabled()) { logger.debug("Read selector running for connections to " + this.getHost() + ":" + this.getPort()); } try { this.selector = Selector.open(); while (this.isActive()) { SocketChannel newChannel; int soTimeout = this.getSoTimeout(); int selectionCount = 0; try { long timeout = soTimeout < 0 ? 0 : soTimeout; if (getDelayedReads().size() > 0 && (timeout == 0 || getReadDelay() < timeout)) { timeout = getReadDelay(); } selectionCount = this.selector.select(timeout); } catch (CancelledKeyException cke) { if (logger.isDebugEnabled()) { logger.debug("CancelledKeyException during Selector.select()"); } } while ((newChannel = this.newChannels.poll()) != null) { try { newChannel.register(this.selector, SelectionKey.OP_READ, this.channelMap.get(newChannel)); } catch (ClosedChannelException cce) { if (logger.isDebugEnabled()) { logger.debug("Channel closed before registering with selector for reading"); } } } this.processNioSelections(selectionCount, this.selector, null, this.channelMap); } } catch (ClosedSelectorException cse) { if (this.isActive()) { logger.error("Selector closed", cse); } } catch (Exception e) { logger.error("Exception in read selector thread", e); this.setActive(false); } if (logger.isDebugEnabled()) { logger.debug("Read selector exiting for connections to " + this.getHost() + ":" + this.getPort()); } } /** * @return the usingDirectBuffers */ protected boolean isUsingDirectBuffers() { return this.usingDirectBuffers; } /** * @return the connections */ protected Map<SocketChannel, TcpNioConnection> getConnections() { return this.channelMap; } /** * @return the newChannels */ protected BlockingQueue<SocketChannel> getNewChannels() { return this.newChannels; } }