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.socket.nio;
21  
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.net.SocketAddress;
25  import java.nio.channels.SelectionKey;
26  import java.nio.channels.Selector;
27  import java.nio.channels.ServerSocketChannel;
28  import java.nio.channels.SocketChannel;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  
36  import org.apache.mina.common.ExceptionMonitor;
37  import org.apache.mina.common.IoAcceptor;
38  import org.apache.mina.common.IoHandler;
39  import org.apache.mina.common.IoServiceConfig;
40  import org.apache.mina.common.support.BaseIoAcceptor;
41  import org.apache.mina.util.NamePreservingRunnable;
42  import org.apache.mina.util.NewThreadExecutor;
43  import org.apache.mina.util.Queue;
44  
45  import edu.emory.mathcs.backport.java.util.concurrent.Executor;
46  
47  /**
48   * {@link IoAcceptor} for socket transport (TCP/IP).
49   *
50   * @author The Apache Directory Project (mina-dev@directory.apache.org)
51   * @version $Rev: 389042 $, $Date: 2006-03-27 07:49:41Z $
52   */
53  public class SocketAcceptor extends BaseIoAcceptor {
54      /**
55       * @noinspection StaticNonFinalField
56       */
57      private static volatile int nextId = 0;
58  
59      private final Executor executor;
60  
61      private final Object lock = new Object();
62  
63      private final int id = nextId++;
64  
65      private final String threadName = "SocketAcceptor-" + id;
66  
67      private SocketAcceptorConfig defaultConfig = new SocketAcceptorConfig();
68  
69      private final Map channels = new HashMap();
70  
71      private final Queue registerQueue = new Queue();
72  
73      private final Queue cancelQueue = new Queue();
74  
75      private final SocketIoProcessor[] ioProcessors;
76  
77      private final int processorCount;
78  
79      /**
80       * @noinspection FieldAccessedSynchronizedAndUnsynchronized
81       */
82      private Selector selector;
83  
84      private Worker worker;
85  
86      private int processorDistributor = 0;
87  
88      /**
89       * Create an acceptor with a single processing thread using a NewThreadExecutor
90       */
91      public SocketAcceptor() {
92          this(1, new NewThreadExecutor());
93      }
94  
95      /**
96       * Create an acceptor with the desired number of processing threads
97       *
98       * @param processorCount Number of processing threads
99       * @param executor Executor to use for launching threads
100      */
101     public SocketAcceptor(int processorCount, Executor executor) {
102         if (processorCount < 1) {
103             throw new IllegalArgumentException(
104                     "Must have at least one processor");
105         }
106 
107         // The default reuseAddress of an accepted socket should be 'true'.
108         ((SocketSessionConfig) defaultConfig.getSessionConfig())
109                 .setReuseAddress(true);
110 
111         this.executor = executor;
112         this.processorCount = processorCount;
113         ioProcessors = new SocketIoProcessor[processorCount];
114 
115         for (int i = 0; i < processorCount; i++) {
116             ioProcessors[i] = new SocketIoProcessor(
117                     "SocketAcceptorIoProcessor-" + id + "." + i, executor);
118         }
119     }
120 
121     /**
122      * Binds to the specified <code>address</code> and handles incoming connections with the specified
123      * <code>handler</code>.  Backlog value is configured to the value of <code>backlog</code> property.
124      *
125      * @throws IOException if failed to bind
126      */
127     public void bind(SocketAddress address, IoHandler handler,
128             IoServiceConfig config) throws IOException {
129         if (handler == null) {
130             throw new NullPointerException("handler");
131         }
132 
133         if (address != null && !(address instanceof InetSocketAddress)) {
134             throw new IllegalArgumentException("Unexpected address type: "
135                     + address.getClass());
136         }
137 
138         if (config == null) {
139             config = getDefaultConfig();
140         }
141 
142         RegistrationRequest request = new RegistrationRequest(address, handler,
143                 config);
144 
145         synchronized (lock) {
146             startupWorker();
147     
148             synchronized (registerQueue) {
149                 registerQueue.push(request);
150             }
151     
152             selector.wakeup();
153         }
154 
155         synchronized (request) {
156             while (!request.done) {
157                 try {
158                     request.wait();
159                 } catch (InterruptedException e) {
160                     ExceptionMonitor.getInstance().exceptionCaught(e);
161                 }
162             }
163         }
164 
165         if (request.exception != null) {
166             throw request.exception;
167         }
168     }
169     
170     private Selector getSelector() {
171         synchronized (lock) {
172             return this.selector;
173         }
174     }
175 
176     private void startupWorker() throws IOException {
177         synchronized (lock) {
178             if (worker == null) {
179                 selector = Selector.open();
180                 worker = new Worker();
181 
182                 executor.execute(new NamePreservingRunnable(worker, threadName));
183             }
184         }
185     }
186 
187     public void unbind(SocketAddress address) {
188         if (address == null) {
189             throw new NullPointerException("address");
190         }
191 
192         CancellationRequest request = new CancellationRequest(address);
193 
194         try {
195             startupWorker();
196         } catch (IOException e) {
197             // IOException is thrown only when Worker thread is not
198             // running and failed to open a selector.  We simply throw
199             // IllegalArgumentException here because we can simply
200             // conclude that nothing is bound to the selector.
201             throw new IllegalArgumentException("Address not bound: " + address);
202         }
203 
204         synchronized (cancelQueue) {
205             cancelQueue.push(request);
206         }
207 
208         selector.wakeup();
209 
210         synchronized (request) {
211             while (!request.done) {
212                 try {
213                     request.wait();
214                 } catch (InterruptedException e) {
215                     ExceptionMonitor.getInstance().exceptionCaught(e);
216                 }
217             }
218         }
219 
220         if (request.exception != null) {
221             request.exception.fillInStackTrace();
222 
223             throw request.exception;
224         }
225     }
226 
227     public void unbindAll() {
228         List addresses;
229         synchronized (channels) {
230             addresses = new ArrayList(channels.keySet());
231         }
232 
233         for (Iterator i = addresses.iterator(); i.hasNext();) {
234             unbind((SocketAddress) i.next());
235         }
236     }
237 
238     private class Worker implements Runnable {
239         public void run() {
240             Selector selector = getSelector();
241             for (;;) {
242                 try {
243                     int nKeys = selector.select();
244 
245                     registerNew();
246 
247                     if (nKeys > 0) {
248                         processSessions(selector.selectedKeys());
249                     }
250 
251                     cancelKeys();
252 
253                     if (selector.keys().isEmpty()) {
254                         synchronized (lock) {
255                             if (selector.keys().isEmpty()
256                                     && registerQueue.isEmpty()
257                                     && cancelQueue.isEmpty()) {
258                                 worker = null;
259                                 try {
260                                     selector.close();
261                                 } catch (IOException e) {
262                                     ExceptionMonitor.getInstance()
263                                             .exceptionCaught(e);
264                                 } finally {
265                                     SocketAcceptor.this.selector = null;
266                                 }
267                                 break;
268                             }
269                         }
270                     }
271                 } catch (IOException e) {
272                     ExceptionMonitor.getInstance().exceptionCaught(e);
273 
274                     try {
275                         Thread.sleep(1000);
276                     } catch (InterruptedException e1) {
277                         ExceptionMonitor.getInstance().exceptionCaught(e1);
278                     }
279                 }
280             }
281         }
282 
283         private void processSessions(Set keys) throws IOException {
284             Iterator it = keys.iterator();
285             while (it.hasNext()) {
286                 SelectionKey key = (SelectionKey) it.next();
287 
288                 it.remove();
289 
290                 if (!key.isAcceptable()) {
291                     continue;
292                 }
293 
294                 ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
295 
296                 SocketChannel ch = ssc.accept();
297 
298                 if (ch == null) {
299                     continue;
300                 }
301 
302                 boolean success = false;
303                 try {
304                     RegistrationRequest req = (RegistrationRequest) key
305                             .attachment();
306                     SocketSessionImpl session = new SocketSessionImpl(
307                             SocketAcceptor.this, nextProcessor(),
308                             getListeners(), req.config, ch, req.handler,
309                             req.address);
310                     getFilterChainBuilder().buildFilterChain(
311                             session.getFilterChain());
312                     req.config.getFilterChainBuilder().buildFilterChain(
313                             session.getFilterChain());
314                     req.config.getThreadModel().buildFilterChain(
315                             session.getFilterChain());
316                     session.getIoProcessor().addNew(session);
317                     success = true;
318                 } catch (Throwable t) {
319                     ExceptionMonitor.getInstance().exceptionCaught(t);
320                 } finally {
321                     if (!success) {
322                         ch.close();
323                     }
324                 }
325             }
326         }
327     }
328 
329     private SocketIoProcessor nextProcessor() {
330         if (this.processorDistributor == Integer.MAX_VALUE) {
331             this.processorDistributor = Integer.MAX_VALUE % this.processorCount;
332         }
333 
334         return ioProcessors[processorDistributor++ % processorCount];
335     }
336 
337     public IoServiceConfig getDefaultConfig() {
338         return defaultConfig;
339     }
340 
341     /**
342      * Sets the config this acceptor will use by default.
343      * 
344      * @param defaultConfig the default config.
345      * @throws NullPointerException if the specified value is <code>null</code>.
346      */
347     public void setDefaultConfig(SocketAcceptorConfig defaultConfig) {
348         if (defaultConfig == null) {
349             throw new NullPointerException("defaultConfig");
350         }
351         this.defaultConfig = defaultConfig;
352     }
353 
354     private void registerNew() {
355         if (registerQueue.isEmpty()) {
356             return;
357         }
358 
359         Selector selector = getSelector();
360         for (;;) {
361             RegistrationRequest req;
362 
363             synchronized (registerQueue) {
364                 req = (RegistrationRequest) registerQueue.pop();
365             }
366 
367             if (req == null) {
368                 break;
369             }
370 
371             ServerSocketChannel ssc = null;
372 
373             try {
374                 ssc = ServerSocketChannel.open();
375                 ssc.configureBlocking(false);
376 
377                 // Configure the server socket,
378                 SocketAcceptorConfig cfg;
379                 if (req.config instanceof SocketAcceptorConfig) {
380                     cfg = (SocketAcceptorConfig) req.config;
381                 } else {
382                     cfg = (SocketAcceptorConfig) getDefaultConfig();
383                 }
384 
385                 ssc.socket().setReuseAddress(cfg.isReuseAddress());
386                 ssc.socket().setReceiveBufferSize(
387                         ((SocketSessionConfig) cfg.getSessionConfig())
388                                 .getReceiveBufferSize());
389 
390                 // and bind.
391                 ssc.socket().bind(req.address, cfg.getBacklog());
392                 if (req.address == null || req.address.getPort() == 0) {
393                     req.address = (InetSocketAddress) ssc.socket()
394                             .getLocalSocketAddress();
395                 }
396                 ssc.register(selector, SelectionKey.OP_ACCEPT, req);
397 
398                 synchronized (channels) {
399                     channels.put(req.address, ssc);
400                 }
401 
402                 getListeners().fireServiceActivated(this, req.address,
403                         req.handler, req.config);
404             } catch (IOException e) {
405                 req.exception = e;
406             } finally {
407                 synchronized (req) {
408                     req.done = true;
409 
410                     req.notifyAll();
411                 }
412 
413                 if (ssc != null && req.exception != null) {
414                     try {
415                         ssc.close();
416                     } catch (IOException e) {
417                         ExceptionMonitor.getInstance().exceptionCaught(e);
418                     }
419                 }
420             }
421         }
422     }
423 
424     private void cancelKeys() {
425         if (cancelQueue.isEmpty()) {
426             return;
427         }
428 
429         Selector selector = getSelector();
430         for (;;) {
431             CancellationRequest request;
432 
433             synchronized (cancelQueue) {
434                 request = (CancellationRequest) cancelQueue.pop();
435             }
436 
437             if (request == null) {
438                 break;
439             }
440 
441             ServerSocketChannel ssc;
442             synchronized (channels) {
443                 ssc = (ServerSocketChannel) channels.remove(request.address);
444             }
445 
446             // close the channel
447             try {
448                 if (ssc == null) {
449                     request.exception = new IllegalArgumentException(
450                             "Address not bound: " + request.address);
451                 } else {
452                     SelectionKey key = ssc.keyFor(selector);
453                     request.registrationRequest = (RegistrationRequest) key
454                             .attachment();
455                     key.cancel();
456 
457                     selector.wakeup(); // wake up again to trigger thread death
458 
459                     ssc.close();
460                 }
461             } catch (IOException e) {
462                 ExceptionMonitor.getInstance().exceptionCaught(e);
463             } finally {
464                 synchronized (request) {
465                     request.done = true;
466                     request.notifyAll();
467                 }
468 
469                 if (request.exception == null) {
470                     getListeners().fireServiceDeactivated(this,
471                             request.address,
472                             request.registrationRequest.handler,
473                             request.registrationRequest.config);
474                 }
475             }
476         }
477     }
478 
479     private static class RegistrationRequest {
480         private InetSocketAddress address;
481 
482         private final IoHandler handler;
483 
484         private final IoServiceConfig config;
485 
486         private IOException exception;
487 
488         private boolean done;
489 
490         private RegistrationRequest(SocketAddress address, IoHandler handler,
491                 IoServiceConfig config) {
492             this.address = (InetSocketAddress) address;
493             this.handler = handler;
494             this.config = config;
495         }
496     }
497 
498     private static class CancellationRequest {
499         private final SocketAddress address;
500 
501         private boolean done;
502 
503         private RegistrationRequest registrationRequest;
504 
505         private RuntimeException exception;
506 
507         private CancellationRequest(SocketAddress address) {
508             this.address = address;
509         }
510     }
511 }