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.support;
21  
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.net.SocketAddress;
25  import java.nio.channels.DatagramChannel;
26  import java.nio.channels.SelectionKey;
27  import java.nio.channels.Selector;
28  import java.util.Iterator;
29  import java.util.Queue;
30  import java.util.Set;
31  import java.util.concurrent.ConcurrentLinkedQueue;
32  import java.util.concurrent.Executor;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.mina.common.ByteBuffer;
36  import org.apache.mina.common.ConnectFuture;
37  import org.apache.mina.common.ExceptionMonitor;
38  import org.apache.mina.common.IoConnector;
39  import org.apache.mina.common.IoHandler;
40  import org.apache.mina.common.IoServiceConfig;
41  import org.apache.mina.common.IoSession;
42  import org.apache.mina.common.IoSessionRecycler;
43  import org.apache.mina.common.IoFilter.WriteRequest;
44  import org.apache.mina.common.support.AbstractIoFilterChain;
45  import org.apache.mina.common.support.BaseIoConnector;
46  import org.apache.mina.common.support.DefaultConnectFuture;
47  import org.apache.mina.transport.socket.nio.DatagramConnectorConfig;
48  import org.apache.mina.transport.socket.nio.DatagramServiceConfig;
49  import org.apache.mina.transport.socket.nio.DatagramSessionConfig;
50  import org.apache.mina.util.NamePreservingRunnable;
51  
52  /**
53   * {@link IoConnector} for datagram transport (UDP/IP).
54   *
55   * @author The Apache Directory Project (mina-dev@directory.apache.org)
56   * @version $Rev: 575603 $, $Date: 2007-09-14 19:04:45 +0900 (Fri, 14 Sep 2007) $
57   */
58  public class DatagramConnectorDelegate extends BaseIoConnector implements
59          DatagramService {
60      private static final AtomicInteger nextId = new AtomicInteger();
61  
62      private final Object lock = new Object();
63  
64      private final IoConnector wrapper;
65  
66      private final Executor executor;
67  
68      private final int id = nextId.getAndIncrement();
69  
70      private volatile Selector selector;
71  
72      private DatagramConnectorConfig defaultConfig = new DatagramConnectorConfig();
73  
74      private final Queue<RegistrationRequest> registerQueue = new ConcurrentLinkedQueue<RegistrationRequest>();
75  
76      private final Queue<DatagramSessionImpl> cancelQueue = new ConcurrentLinkedQueue<DatagramSessionImpl>();
77  
78      private final Queue<DatagramSessionImpl> flushingSessions = new ConcurrentLinkedQueue<DatagramSessionImpl>();
79  
80      private final Queue<DatagramSessionImpl> trafficControllingSessions = new ConcurrentLinkedQueue<DatagramSessionImpl>();
81  
82      private Worker worker;
83  
84      /**
85       * Creates a new instance.
86       */
87      public DatagramConnectorDelegate(IoConnector wrapper, Executor executor) {
88          this.wrapper = wrapper;
89          this.executor = executor;
90      }
91  
92      public ConnectFuture connect(SocketAddress address, IoHandler handler,
93              IoServiceConfig config) {
94          return connect(address, null, handler, config);
95      }
96  
97      public ConnectFuture connect(SocketAddress address,
98              SocketAddress localAddress, IoHandler handler,
99              IoServiceConfig config) {
100         if (address == null)
101             throw new NullPointerException("address");
102         if (handler == null)
103             throw new NullPointerException("handler");
104 
105         if (!(address instanceof InetSocketAddress))
106             throw new IllegalArgumentException("Unexpected address type: "
107                     + address.getClass());
108 
109         if (localAddress != null
110                 && !(localAddress instanceof InetSocketAddress)) {
111             throw new IllegalArgumentException(
112                     "Unexpected local address type: " + localAddress.getClass());
113         }
114 
115         if (config == null) {
116             config = getDefaultConfig();
117         }
118 
119         DatagramChannel ch = null;
120         boolean initialized = false;
121         try {
122             ch = DatagramChannel.open();
123             DatagramSessionConfig cfg;
124             if (config.getSessionConfig() instanceof DatagramSessionConfig) {
125                 cfg = (DatagramSessionConfig) config.getSessionConfig();
126             } else {
127                 cfg = getDefaultConfig().getSessionConfig();
128             }
129 
130             ch.socket().setReuseAddress(cfg.isReuseAddress());
131             ch.socket().setBroadcast(cfg.isBroadcast());
132             ch.socket().setReceiveBufferSize(cfg.getReceiveBufferSize());
133             ch.socket().setSendBufferSize(cfg.getSendBufferSize());
134 
135             if (ch.socket().getTrafficClass() != cfg.getTrafficClass()) {
136                 ch.socket().setTrafficClass(cfg.getTrafficClass());
137             }
138 
139             if (localAddress != null) {
140                 ch.socket().bind(localAddress);
141             }
142             ch.connect(address);
143             ch.configureBlocking(false);
144             initialized = true;
145         } catch (IOException e) {
146             return DefaultConnectFuture.newFailedFuture(e);
147         } finally {
148             if (!initialized && ch != null) {
149                 try {
150                     ch.disconnect();
151                     ch.close();
152                 } catch (IOException e) {
153                     ExceptionMonitor.getInstance().exceptionCaught(e);
154                 }
155             }
156         }
157 
158         RegistrationRequest request = new RegistrationRequest(ch, handler,
159                 config);
160         synchronized (lock) {
161             try {
162                 startupWorker();
163             } catch (IOException e) {
164                 try {
165                     ch.disconnect();
166                     ch.close();
167                 } catch (IOException e2) {
168                     ExceptionMonitor.getInstance().exceptionCaught(e2);
169                 }
170     
171                 return DefaultConnectFuture.newFailedFuture(e);
172             }
173     
174             registerQueue.add(request);
175     
176             selector.wakeup();
177         }
178         return request;
179     }
180 
181     public DatagramConnectorConfig getDefaultConfig() {
182         return defaultConfig;
183     }
184 
185     /**
186      * Sets the config this connector will use by default.
187      *
188      * @param defaultConfig the default config.
189      * @throws NullPointerException if the specified value is <code>null</code>.
190      */
191     public void setDefaultConfig(DatagramConnectorConfig defaultConfig) {
192         if (defaultConfig == null) {
193             throw new NullPointerException("defaultConfig");
194         }
195         this.defaultConfig = defaultConfig;
196     }
197 
198     private void startupWorker() throws IOException {
199         synchronized (lock) {
200             if (worker == null) {
201                 selector = Selector.open();
202                 worker = new Worker();
203                 executor.execute(new NamePreservingRunnable(worker));
204             }
205         }
206     }
207 
208     public void closeSession(DatagramSessionImpl session) {
209         synchronized (lock) {
210             try {
211                 startupWorker();
212             } catch (IOException e) {
213                 // IOException is thrown only when Worker thread is not
214                 // running and failed to open a selector.  We simply return
215                 // silently here because it we can simply conclude that
216                 // this session is not managed by this connector or
217                 // already closed.
218                 return;
219             }
220     
221             cancelQueue.add(session);
222     
223             selector.wakeup();
224         }
225     }
226 
227     public void flushSession(DatagramSessionImpl session) {
228         if (scheduleFlush(session)) {
229             Selector selector = this.selector;
230             if (selector != null) {
231                 selector.wakeup();
232             }
233         }
234     }
235 
236     private boolean scheduleFlush(DatagramSessionImpl session) {
237         if (session.setScheduledForFlush(true)) {
238             flushingSessions.add(session);
239             return true;
240         } else {
241             return false;
242         }
243     }
244 
245     public void updateTrafficMask(DatagramSessionImpl session) {
246         scheduleTrafficControl(session);
247         Selector selector = this.selector;
248         if (selector != null) {
249             selector.wakeup();
250         }
251     }
252 
253     private void scheduleTrafficControl(DatagramSessionImpl session) {
254         trafficControllingSessions.add(session);
255     }
256 
257     private void doUpdateTrafficMask() {
258         if (trafficControllingSessions.isEmpty())
259             return;
260 
261         for (;;) {
262             DatagramSessionImpl session = trafficControllingSessions.poll();
263 
264             if (session == null)
265                 break;
266 
267             SelectionKey key = session.getSelectionKey();
268             // Retry later if session is not yet fully initialized.
269             // (In case that Session.suspend??() or session.resume??() is
270             // called before addSession() is processed)
271             if (key == null) {
272                 scheduleTrafficControl(session);
273                 break;
274             }
275             // skip if channel is already closed
276             if (!key.isValid()) {
277                 continue;
278             }
279 
280             // The normal is OP_READ and, if there are write requests in the
281             // session's write queue, set OP_WRITE to trigger flushing.
282             int ops = SelectionKey.OP_READ;
283             if (!session.getWriteRequestQueue().isEmpty()) {
284                 ops |= SelectionKey.OP_WRITE;
285             }
286 
287             // Now mask the preferred ops with the mask of the current session
288             int mask = session.getTrafficMask().getInterestOps();
289             key.interestOps(ops & mask);
290         }
291     }
292 
293     private class Worker implements Runnable {
294         public void run() {
295             Thread.currentThread().setName("DatagramConnector-" + id);
296 
297             Selector selector = DatagramConnectorDelegate.this.selector;
298             for (;;) {
299                 try {
300                     int nKeys = selector.select();
301 
302                     registerNew();
303                     doUpdateTrafficMask();
304 
305                     if (nKeys > 0) {
306                         processReadySessions(selector.selectedKeys());
307                     }
308 
309                     flushSessions();
310                     cancelKeys();
311 
312                     if (selector.keys().isEmpty()) {
313                         synchronized (lock) {
314                             if (selector.keys().isEmpty()
315                                     && registerQueue.isEmpty()
316                                     && cancelQueue.isEmpty()) {
317                                 worker = null;
318                                 try {
319                                     selector.close();
320                                 } catch (IOException e) {
321                                     ExceptionMonitor.getInstance()
322                                             .exceptionCaught(e);
323                                 } finally {
324                                     DatagramConnectorDelegate.this.selector = null;
325                                 }
326                                 break;
327                             }
328                         }
329                     }
330                 } catch (IOException e) {
331                     ExceptionMonitor.getInstance().exceptionCaught(e);
332 
333                     try {
334                         Thread.sleep(1000);
335                     } catch (InterruptedException e1) {
336                         ExceptionMonitor.getInstance().exceptionCaught(e1);
337                     }
338                 }
339             }
340         }
341     }
342 
343     private void processReadySessions(Set<SelectionKey> keys) {
344         Iterator<SelectionKey> it = keys.iterator();
345         while (it.hasNext()) {
346             SelectionKey key = it.next();
347             it.remove();
348 
349             DatagramSessionImpl session = (DatagramSessionImpl) key
350                     .attachment();
351 
352             // Let the recycler know that the session is still active. 
353             getSessionRecycler(session).recycle(session.getLocalAddress(),
354                     session.getRemoteAddress());
355 
356             if (key.isReadable() && session.getTrafficMask().isReadable()) {
357                 readSession(session);
358             }
359 
360             if (key.isWritable() && session.getTrafficMask().isWritable()) {
361                 scheduleFlush(session);
362             }
363         }
364     }
365 
366     private IoSessionRecycler getSessionRecycler(IoSession session) {
367         IoServiceConfig config = session.getServiceConfig();
368         IoSessionRecycler sessionRecycler;
369         if (config instanceof DatagramServiceConfig) {
370             sessionRecycler = ((DatagramServiceConfig) config)
371                     .getSessionRecycler();
372         } else {
373             sessionRecycler = defaultConfig.getSessionRecycler();
374         }
375         return sessionRecycler;
376     }
377 
378     private void readSession(DatagramSessionImpl session) {
379 
380         ByteBuffer readBuf = ByteBuffer.allocate(session.getReadBufferSize());
381         try {
382             int readBytes = session.getChannel().read(readBuf.buf());
383             if (readBytes > 0) {
384                 readBuf.flip();
385                 ByteBuffer newBuf = ByteBuffer.allocate(readBuf.limit());
386                 newBuf.put(readBuf);
387                 newBuf.flip();
388 
389                 session.increaseReadBytes(readBytes);
390                 session.getFilterChain().fireMessageReceived(session, newBuf);
391             }
392         } catch (IOException e) {
393             session.getFilterChain().fireExceptionCaught(session, e);
394         } finally {
395             readBuf.release();
396         }
397     }
398 
399     private void flushSessions() {
400         if (flushingSessions.size() == 0)
401             return;
402 
403         for (;;) {
404             DatagramSessionImpl session = flushingSessions.poll();
405 
406             if (session == null)
407                 break;
408 
409             session.setScheduledForFlush(false);
410 
411             try {
412                 boolean flushedAll = flush(session);
413                 if (flushedAll && !session.getWriteRequestQueue().isEmpty() && !session.isScheduledForFlush()) {
414                     scheduleFlush(session);
415                 }
416             } catch (IOException e) {
417                 session.getFilterChain().fireExceptionCaught(session, e);
418             }
419         }
420     }
421 
422     private boolean flush(DatagramSessionImpl session) throws IOException {
423         // Clear OP_WRITE
424         SelectionKey key = session.getSelectionKey();
425         if (key == null) {
426             scheduleFlush(session);
427             return false;
428         }
429         if (!key.isValid()) {
430             return false;
431         }
432         key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
433 
434         DatagramChannel ch = session.getChannel();
435         Queue<WriteRequest> writeRequestQueue = session.getWriteRequestQueue();
436 
437         int writtenBytes = 0;
438         int maxWrittenBytes = ((DatagramSessionConfig) session.getConfig()).getSendBufferSize() << 1;
439         try {
440             for (;;) {
441                 WriteRequest req = writeRequestQueue.peek();
442         
443                 if (req == null)
444                     break;
445         
446                 ByteBuffer buf = (ByteBuffer) req.getMessage();
447                 if (buf.remaining() == 0) {
448                     // pop and fire event
449                     writeRequestQueue.poll();
450         
451                     session.increaseWrittenMessages();
452                     buf.reset();
453                     session.getFilterChain().fireMessageSent(session, req);
454                     continue;
455                 }
456         
457                 int localWrittenBytes = ch.write(buf.buf());
458                 writtenBytes += localWrittenBytes;
459     
460                 if (localWrittenBytes == 0 || writtenBytes >= maxWrittenBytes) {
461                     // Kernel buffer is full or wrote too much
462                     key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
463                     return false;
464                 } else {
465                     key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
466     
467                     // pop and fire event
468                     writeRequestQueue.poll();
469     
470                     session.increaseWrittenMessages();
471                     buf.reset();
472                     session.getFilterChain().fireMessageSent(session, req);
473                 }
474             }
475         } finally {
476             session.increaseWrittenBytes(writtenBytes);
477         }
478         
479         return true;
480     }
481 
482     private void registerNew() {
483         if (registerQueue.isEmpty())
484             return;
485 
486         Selector selector = this.selector;
487         for (;;) {
488             RegistrationRequest req = registerQueue.poll();
489 
490             if (req == null)
491                 break;
492 
493             DatagramSessionImpl session = new DatagramSessionImpl(wrapper,
494                     this, req.config, req.channel, req.handler, req.channel
495                             .socket().getRemoteSocketAddress(), req.channel
496                             .socket().getLocalSocketAddress());
497 
498             // AbstractIoFilterChain will notify the connect future.
499             session.setAttribute(AbstractIoFilterChain.CONNECT_FUTURE, req);
500 
501             boolean success = false;
502             try {
503                 SelectionKey key = req.channel.register(selector,
504                         SelectionKey.OP_READ, session);
505 
506                 session.setSelectionKey(key);
507                 buildFilterChain(req, session);
508                 getSessionRecycler(session).put(session);
509 
510                 // The CONNECT_FUTURE attribute is cleared and notified here.
511                 getListeners().fireSessionCreated(session);
512                 success = true;
513             } catch (Throwable t) {
514                 // The CONNECT_FUTURE attribute is cleared and notified here.
515                 session.getFilterChain().fireExceptionCaught(session, t);
516             } finally {
517                 if (!success) {
518                     try {
519                         req.channel.disconnect();
520                         req.channel.close();
521                     } catch (IOException e) {
522                         ExceptionMonitor.getInstance().exceptionCaught(e);
523                     }
524                 }
525             }
526         }
527     }
528 
529     private void buildFilterChain(RegistrationRequest req, IoSession session)
530             throws Exception {
531         getFilterChainBuilder().buildFilterChain(session.getFilterChain());
532         req.config.getFilterChainBuilder().buildFilterChain(
533                 session.getFilterChain());
534         req.config.getThreadModel().buildFilterChain(session.getFilterChain());
535     }
536 
537     private void cancelKeys() {
538         if (cancelQueue.isEmpty())
539             return;
540 
541         Selector selector = this.selector;
542         for (;;) {
543             DatagramSessionImpl session = cancelQueue.poll();
544 
545             if (session == null)
546                 break;
547             else {
548                 SelectionKey key = session.getSelectionKey();
549                 DatagramChannel ch = (DatagramChannel) key.channel();
550                 try {
551                     ch.disconnect();
552                     ch.close();
553                 } catch (IOException e) {
554                     ExceptionMonitor.getInstance().exceptionCaught(e);
555                 }
556 
557                 getListeners().fireSessionDestroyed(session);
558                 session.getCloseFuture().setClosed();
559                 key.cancel();
560                 selector.wakeup(); // wake up again to trigger thread death
561             }
562         }
563     }
564 
565     private static class RegistrationRequest extends DefaultConnectFuture {
566         private final DatagramChannel channel;
567 
568         private final IoHandler handler;
569 
570         private final IoServiceConfig config;
571 
572         private RegistrationRequest(DatagramChannel channel, IoHandler handler,
573                 IoServiceConfig config) {
574             this.channel = channel;
575             this.handler = handler;
576             this.config = config;
577         }
578     }
579 }