View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.transport.nio;
    21
    22  import java.io.IOException;
    23  import java.net.SocketAddress;
    24  import java.net.SocketException;
    25  import java.nio.channels.SelectionKey;
    26  import java.nio.channels.SocketChannel;
    27
    28  import org.apache.mina.api.IdleStatus;
    29  import org.apache.mina.api.IoFuture;
    30  import org.apache.mina.api.IoSession;
    31  import org.apache.mina.api.MinaRuntimeException;
    32  import org.apache.mina.service.executor.IoHandlerExecutor;
    33  import org.apache.mina.service.executor.OrderedHandlerExecutor;
    34  import org.apache.mina.service.idlechecker.IdleChecker;
    35  import org.apache.mina.service.idlechecker.IndexedIdleChecker;
    36  import org.apache.mina.transport.ConnectFuture;
    37  import org.apache.mina.transport.tcp.AbstractTcpClient;
    38  import org.apache.mina.transport.tcp.TcpSessionConfig;
    39  import org.apache.mina.util.Assert;
    40  import org.slf4j.Logger;
    41  import org.slf4j.LoggerFactory;
    42
    43  /**
    44   * This class implements a TCP NIO based client.
    45   *
    46   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    47   */
    48  public class NioTcpClient extends AbstractTcpClient {
    49
    50      /** A logger for this class */
    51      private static final Logger LOG = LoggerFactory.getLogger(NioTcpClient.class);
    52
    53      /** the SelectorLoop for connecting the sessions */
    54      // This is final, so that we know if it's not initialized
    55      private final SelectorLoop connectSelectorLoop;
    56
    57      /** the Selectorloop for handling read/write session events */
    58      // This is final, so that we know if it's not initialized
    59      private final SelectorLoopPool readWriteSelectorPool;
    60
    61      /** for detecting idle session */
    62      private IdleChecker idleChecker;
    63
    64      /**
    65       * Create a TCP client with new selector pool of default size and a {@link IoHandlerExecutor} of default type (
    66       * {@link OrderedHandlerExecutor})
    67       */
    68      public NioTcpClient() {
    69          // Default to 2 threads in the pool
    70          this(new NioSelectorLoop("connect", 0), new FixedSelectorLoopPool("Client", 2), null);
    71      }
    72
    73      /**
    74       * Create a TCP client with provided selector loops pool. We will use one SelectorLoop get from the pool to manage
    75       * the OP_CONNECT events. If the pool contains only one SelectorLoop, then all the events will be managed by the
    76       * same Selector.
    77       *
    78       * @param readWriteSelectorLoop the pool of selector loop for handling read/write events of connected sessions
    79       * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
    80       *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
    81       *        operations.
    82       */
    83      public NioTcpClient(SelectorLoopPool selectorLoopPool, IoHandlerExecutor handlerExecutor) {
    84          super(handlerExecutor);
    85          connectSelectorLoop = selectorLoopPool.getSelectorLoop();
    86          readWriteSelectorPool = selectorLoopPool;
    87      }
    88
    89      /**
    90       * Create a TCP client with provided selector loops pool
    91       *
    92       * @param connectSelectorLoop the selector loop for handling connection events (connection of new session)
    93       * @param readWriteSelectorLoop the pool of selector loop for handling read/write events of connected sessions
    94       * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
    95       *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
    96       *        operations.
    97       */
    98      public NioTcpClient(SelectorLoop connectSelectorLoop, SelectorLoopPool readWriteSelectorLoop,
    99              IoHandlerExecutor handlerExecutor) {
    100         super(handlerExecutor);
    101         this.connectSelectorLoop = connectSelectorLoop;
    102         this.readWriteSelectorPool = readWriteSelectorLoop;
    103         idleChecker = new IndexedIdleChecker();
    104     }
    105
    106     /**
    107      * {@inheritDoc}
    108      */
    109     @Override
    110     public IoFuture<IoSession> connect(SocketAddress remoteAddress) {
    111         Assert.assertNotNull(remoteAddress, "remoteAddress");
    112
    113         SocketChannel clientSocket;
    114         try {
    115             clientSocket = SocketChannel.open();
    116         } catch (IOException e) {
    117             throw new MinaRuntimeException("can't create a new socket, out of file descriptors ?", e);
    118         }
    119
    120         try {
    121             clientSocket.socket().setSoTimeout(getConnectTimeoutMillis());
    122         } catch (SocketException e) {
    123             throw new MinaRuntimeException("can't set socket timeout", e);
    124         }
    125
    126         // non blocking
    127         try {
    128             clientSocket.configureBlocking(false);
    129         } catch (IOException e) {
    130             throw new MinaRuntimeException("can't configure socket as non-blocking", e);
    131         }
    132
    133         // apply idle configuration
    134         // Has to be final, as it's used in a inner class...
    135         final NioTcpSession session = new NioTcpSession(this, clientSocket, readWriteSelectorPool.getSelectorLoop(),
    136                 idleChecker);
    137         TcpSessionConfig config = getSessionConfig();
    138
    139         session.getConfig().setIdleTimeInMillis(IdleStatus.READ_IDLE, config.getIdleTimeInMillis(IdleStatus.READ_IDLE));
    140         session.getConfig().setIdleTimeInMillis(IdleStatus.WRITE_IDLE,
    141                 config.getIdleTimeInMillis(IdleStatus.WRITE_IDLE));
    142
    143         // apply the default service socket configuration
    144         Boolean keepAlive = config.isKeepAlive();
    145
    146         if (keepAlive != null) {
    147             session.getConfig().setKeepAlive(keepAlive);
    148         }
    149
    150         Boolean oobInline = config.isOobInline();
    151
    152         if (oobInline != null) {
    153             session.getConfig().setOobInline(oobInline);
    154         }
    155
    156         Boolean reuseAddress = config.isReuseAddress();
    157
    158         if (reuseAddress != null) {
    159             session.getConfig().setReuseAddress(reuseAddress);
    160         }
    161
    162         Boolean tcpNoDelay = config.isTcpNoDelay();
    163
    164         if (tcpNoDelay != null) {
    165             session.getConfig().setTcpNoDelay(tcpNoDelay);
    166         }
    167
    168         Integer receiveBufferSize = config.getReadBufferSize();
    169
    170         if (receiveBufferSize != null) {
    171             session.getConfig().setReadBufferSize(receiveBufferSize);
    172         } else {
    173             int rcvBufferSize;
    174             try {
    175                 rcvBufferSize = clientSocket.socket().getReceiveBufferSize();
    176             } catch (SocketException e) {
    177                 throw new MinaRuntimeException("can't configure socket receive buffer size", e);
    178             }
    179             session.getConfig().setReadBufferSize(rcvBufferSize);
    180         }
    181
    182         Integer sendBufferSize = config.getSendBufferSize();
    183
    184         if (sendBufferSize != null) {
    185             session.getConfig().setSendBufferSize(sendBufferSize);
    186         } else {
    187             int sndBufferSize;
    188             try {
    189                 sndBufferSize = clientSocket.socket().getSendBufferSize();
    190             } catch (SocketException e) {
    191                 throw new MinaRuntimeException("can't configure socket send buffe size", e);
    192             }
    193             session.getConfig().setSendBufferSize(sndBufferSize);
    194         }
    195
    196         Integer trafficClass = config.getTrafficClass();
    197
    198         if (trafficClass != null) {
    199             session.getConfig().setTrafficClass(trafficClass);
    200         }
    201
    202         Integer soLinger = config.getSoLinger();
    203
    204         if (soLinger != null) {
    205             session.getConfig().setSoLinger(soLinger);
    206         }
    207
    208         // Set the secured flag if the service is to be used over SSL/TLS
    209         if (config.isSecured()) {
    210             session.initSecure(config.getSslContext());
    211         }
    212
    213         // connect to a running server. We get an immediate result if
    214         // the socket is blocking, and either true or false if it's non blocking
    215         boolean connected;
    216         try {
    217             connected = clientSocket.connect(remoteAddress);
    218         } catch (IOException e) {
    219             ConnectFuture future = new ConnectFuture();
    220             future.cannotConnect(e);
    221             return future;
    222         }
    223
    224         ConnectFuture connectFuture = new ConnectFuture();
    225         session.setConnectFuture(connectFuture);
    226
    227         if (!connected) {
    228             // async connection, let's the connection complete in background, the selector loop will detect when the
    229             // connection is successful
    230             connectSelectorLoop.register(false, true, false, false, session, clientSocket, new RegistrationCallback() {
    231
    232                 @Override
    233                 public void done(SelectionKey selectionKey) {
    234                     session.setSelectionKey(selectionKey);
    235                 }
    236             });
    237         } else {
    238             // already connected (probably a loopback connection, or a blocking socket)
    239             // register for read
    240             connectSelectorLoop.register(false, false, true, false, session, clientSocket, new RegistrationCallback() {
    241
    242                 @Override
    243                 public void done(SelectionKey selectionKey) {
    244                     session.setSelectionKey(selectionKey);
    245                 }
    246             });
    247
    248             session.setConnected();
    249         }
    250
    251         return connectFuture;
    252     }
    253
    254     /**
    255      * {@inheritDoc}
    256      */
    257     public synchronized void disconnect() throws IOException {
    258         LOG.info("Disconnecting sessions");
    259
    260         // Close all the existing sessions
    261         for (IoSession session : getManagedSessions().values()) {
    262             session.close(true);
    263         }
    264
    265         fireServiceInactivated();
    266
    267         // will stop the idle processor if we are the last service
    268         idleChecker.destroy();
    269     }
    270 }