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.InetSocketAddress;
24  import java.net.SocketAddress;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.SelectionKey;
27  import java.nio.channels.ServerSocketChannel;
28  import java.nio.channels.SocketChannel;
29  
30  import org.apache.mina.api.IdleStatus;
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.tcp.AbstractTcpServer;
37  import org.apache.mina.transport.tcp.TcpSessionConfig;
38  import org.apache.mina.util.Assert;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * This class implements a TCP NIO based server.
44   * 
45   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
46   */
47  public class NioTcpServer extends AbstractTcpServer implements SelectorListener {
48      /** A logger for this class */
49      static final Logger LOG = LoggerFactory.getLogger(NioTcpServer.class);
50  
51      /** the bound local address */
52      private SocketAddress address = null;
53  
54      private final SelectorLoop acceptSelectorLoop;
55  
56      private final SelectorLoopPool readWriteSelectorPool;
57  
58      // the key used for selecting accept event
59      private SelectionKey acceptKey = null;
60  
61      // the server socket for accepting clients
62      private ServerSocketChannel serverChannel = null;
63  
64      private IdleChecker idleChecker;
65  
66      /**
67       * Create a TCP server with new selector pool of default size and a {@link IoHandlerExecutor} of default type (
68       * {@link OrderedHandlerExecutor})
69       */
70      public NioTcpServer() {
71          this(new NioSelectorLoop("accept", 0), new FixedSelectorLoopPool("Server", Runtime.getRuntime()
72                  .availableProcessors() + 1), null);
73      }
74  
75      /**
76       * Create a TCP server with new selector pool of default size and a {@link IoHandlerExecutor} of default type (
77       * {@link OrderedHandlerExecutor})
78       * 
79       * @param config The specific configuration to use
80       */
81      public NioTcpServer(TcpSessionConfig config) {
82          this(config, new NioSelectorLoop("accept", 0), new FixedSelectorLoopPool("Server", Runtime.getRuntime()
83                  .availableProcessors() + 1), null);
84      }
85  
86      /**
87       * Create a TCP server with provided selector loops pool. We will use one SelectorLoop get from the pool to manage
88       * the OP_ACCEPT events. If the pool contains only one SelectorLoop, then all the events will be managed by the same
89       * Selector.
90       * 
91       * @param selectorLoopPool the selector loop pool for handling all I/O events (accept, read, write)
92       * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
93       *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
94       *        operations.
95       */
96      public NioTcpServer(SelectorLoopPool selectorLoopPool, IoHandlerExecutor handlerExecutor) {
97          this(selectorLoopPool.getSelectorLoop(), selectorLoopPool, handlerExecutor);
98      }
99  
100     /**
101      * Create a TCP server with provided selector loops pool. We will use one SelectorLoop get from the pool to manage
102      * the OP_ACCEPT events. If the pool contains only one SelectorLoop, then all the events will be managed by the same
103      * Selector.
104      * 
105      * @param config The specific configuration to use
106      * @param selectorLoopPool the selector loop pool for handling all I/O events (accept, read, write)
107      * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
108      *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
109      *        operations.
110      */
111     public NioTcpServer(TcpSessionConfig config, SelectorLoopPool selectorLoopPool, IoHandlerExecutor handlerExecutor) {
112         this(config, selectorLoopPool.getSelectorLoop(), selectorLoopPool, handlerExecutor);
113     }
114 
115     /**
116      * Create a TCP server with provided selector loops pool
117      * 
118      * @param acceptSelectorLoop the selector loop for handling accept events (connection of new session)
119      * @param readWriteSelectorLoop the pool of selector loop for handling read/write events of connected sessions
120      * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
121      *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
122      *        operations.
123      */
124     public NioTcpServer(SelectorLoop acceptSelectorLoop, SelectorLoopPool readWriteSelectorLoop,
125             IoHandlerExecutor handlerExecutor) {
126         super(handlerExecutor);
127         this.acceptSelectorLoop = acceptSelectorLoop;
128         this.readWriteSelectorPool = readWriteSelectorLoop;
129     }
130 
131     /**
132      * Create a TCP server with provided selector loops pool
133      * 
134      * @param config The specific configuration to use
135      * @param acceptSelectorLoop the selector loop for handling accept events (connection of new session)
136      * @param readWriteSelectorLoop the pool of selector loop for handling read/write events of connected sessions
137      * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
138      *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
139      *        operations.
140      */
141     public NioTcpServer(TcpSessionConfig config, SelectorLoop acceptSelectorLoop,
142             SelectorLoopPool readWriteSelectorLoop, IoHandlerExecutor handlerExecutor) {
143         super(config, handlerExecutor);
144         this.acceptSelectorLoop = acceptSelectorLoop;
145         this.readWriteSelectorPool = readWriteSelectorLoop;
146     }
147 
148     /**
149      * Get the inner Server socket for accepting new client connections
150      * 
151      * @return
152      */
153     public synchronized ServerSocketChannel getServerSocketChannel() {
154         return serverChannel;
155     }
156 
157     public synchronized void setServerSocketChannel(final ServerSocketChannel serverChannel) {
158         this.serverChannel = serverChannel;
159     }
160 
161     /**
162      * {@inheritDoc}
163      */
164     @Override
165     public void bind(final int port) {
166         bind(new InetSocketAddress(port));
167     }
168 
169     /**
170      * {@inheritDoc}
171      */
172     @Override
173     public synchronized void bind(SocketAddress localAddress) {
174         Assert.assertNotNull(localAddress, "localAddress");
175 
176         // check if the address is already bound
177         if (address != null) {
178             throw new IllegalStateException("address " + address + " already bound");
179         }
180 
181         LOG.info("binding address {}", localAddress);
182         address = localAddress;
183 
184         try {
185             serverChannel = ServerSocketChannel.open();
186             serverChannel.socket().setReuseAddress(isReuseAddress());
187             serverChannel.socket().bind(address);
188             serverChannel.configureBlocking(false);
189         } catch (IOException e) {
190             throw new MinaRuntimeException("can't bind address" + address, e);
191         }
192 
193         acceptSelectorLoop.register(true, false, false, false, this, serverChannel, null);
194 
195         idleChecker = new IndexedIdleChecker();
196         idleChecker.start();
197 
198         // it's the first address bound, let's fire the event
199         fireServiceActivated();
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public SocketAddress getBoundAddress() {
207         return address;
208     }
209 
210     /**
211      * {@inheritDoc}
212      */
213     @Override
214     public synchronized void unbind() {
215         LOG.info("unbinding {}", address);
216         if (this.address == null) {
217             throw new IllegalStateException("server not bound");
218         }
219         try {
220             serverChannel.socket().close();
221             serverChannel.close();
222         } catch (IOException e) {
223             throw new MinaRuntimeException("can't unbind server", e);
224         }
225 
226         acceptSelectorLoop.unregister(this, serverChannel);
227 
228         this.address = null;
229         this.fireServiceInactivated();
230 
231         // will stop the acceptor processor if we are the last service
232         idleChecker.destroy();
233     }
234 
235     /**
236      * @return the acceptKey
237      */
238     public SelectionKey getAcceptKey() {
239         return acceptKey;
240     }
241 
242     /**
243      * @param acceptKey the acceptKey to set
244      */
245     public void setAcceptKey(final SelectionKey acceptKey) {
246         this.acceptKey = acceptKey;
247     }
248 
249     /**
250      * {@inheritDoc}
251      */
252     @Override
253     public void ready(final boolean accept, boolean connect, final boolean read, final ByteBuffer readBuffer,
254             final boolean write) {
255         if (accept) {
256             LOG.debug("acceptable new client");
257 
258             // accepted connection
259             try {
260                 LOG.debug("new client accepted");
261                 createSession(getServerSocketChannel().accept());
262 
263             } catch (final IOException e) {
264                 LOG.error("error while accepting new client", e);
265             }
266         }
267 
268         if (read || write) {
269             throw new IllegalStateException("should not receive read or write events");
270         }
271     }
272 
273     private synchronized void createSession(SocketChannel clientSocket) throws IOException {
274         LOG.debug("create session");
275         SocketChannel socketChannel = clientSocket;
276         TcpSessionConfig config = getSessionConfig();
277         SelectorLoop readWriteSelectorLoop = readWriteSelectorPool.getSelectorLoop();
278         final NioTcpSession session = new NioTcpSession(this, socketChannel, readWriteSelectorLoop, idleChecker);
279 
280         socketChannel.configureBlocking(false);
281 
282         // apply idle configuration
283         session.getConfig().setIdleTimeInMillis(IdleStatus.READ_IDLE, config.getIdleTimeInMillis(IdleStatus.READ_IDLE));
284         session.getConfig().setIdleTimeInMillis(IdleStatus.WRITE_IDLE,
285                 config.getIdleTimeInMillis(IdleStatus.WRITE_IDLE));
286 
287         // apply the default service socket configuration
288         Boolean keepAlive = config.isKeepAlive();
289 
290         if (keepAlive != null) {
291             session.getConfig().setKeepAlive(keepAlive);
292         }
293 
294         Boolean oobInline = config.isOobInline();
295 
296         if (oobInline != null) {
297             session.getConfig().setOobInline(oobInline);
298         }
299 
300         Boolean reuseAddress = config.isReuseAddress();
301 
302         if (reuseAddress != null) {
303             session.getConfig().setReuseAddress(reuseAddress);
304         }
305 
306         Boolean tcpNoDelay = config.isTcpNoDelay();
307 
308         if (tcpNoDelay != null) {
309             session.getConfig().setTcpNoDelay(tcpNoDelay);
310         }
311 
312         Integer receiveBufferSize = config.getReadBufferSize();
313 
314         if (receiveBufferSize != null) {
315             session.getConfig().setReadBufferSize(receiveBufferSize);
316         }
317 
318         Integer sendBufferSize = config.getSendBufferSize();
319 
320         if (sendBufferSize != null) {
321             session.getConfig().setSendBufferSize(sendBufferSize);
322         }
323 
324         Integer trafficClass = config.getTrafficClass();
325 
326         if (trafficClass != null) {
327             session.getConfig().setTrafficClass(trafficClass);
328         }
329 
330         Integer soLinger = config.getSoLinger();
331 
332         if (soLinger != null) {
333             session.getConfig().setSoLinger(soLinger);
334         }
335 
336         // Set the secured flag if the service is to be used over SSL/TLS
337         if (config.isSecured()) {
338             session.initSecure(config.getSslContext());
339         }
340 
341         // add the session to the queue for being added to the selector
342         readWriteSelectorLoop.register(false, false, true, false, session, socketChannel, new RegistrationCallback() {
343 
344             @Override
345             public void done(SelectionKey selectionKey) {
346                 session.setSelectionKey(selectionKey);
347                 session.setConnected();
348             }
349         });
350 
351         idleChecker.sessionRead(session, System.currentTimeMillis());
352         idleChecker.sessionWritten(session, System.currentTimeMillis());
353     }
354 
355 }