1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Queue;
33 import java.util.Set;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.ConcurrentLinkedQueue;
36 import java.util.concurrent.Executor;
37 import java.util.concurrent.atomic.AtomicInteger;
38
39 import org.apache.mina.common.ByteBuffer;
40 import org.apache.mina.common.ExceptionMonitor;
41 import org.apache.mina.common.IoAcceptor;
42 import org.apache.mina.common.IoHandler;
43 import org.apache.mina.common.IoServiceConfig;
44 import org.apache.mina.common.IoSession;
45 import org.apache.mina.common.IoSessionRecycler;
46 import org.apache.mina.common.RuntimeIOException;
47 import org.apache.mina.common.IoFilter.WriteRequest;
48 import org.apache.mina.common.support.BaseIoAcceptor;
49 import org.apache.mina.common.support.IoServiceListenerSupport;
50 import org.apache.mina.transport.socket.nio.DatagramAcceptorConfig;
51 import org.apache.mina.transport.socket.nio.DatagramServiceConfig;
52 import org.apache.mina.transport.socket.nio.DatagramSessionConfig;
53 import org.apache.mina.util.NamePreservingRunnable;
54
55
56
57
58
59
60
61 public class DatagramAcceptorDelegate extends BaseIoAcceptor implements
62 IoAcceptor, DatagramService {
63 private static final AtomicInteger nextId = new AtomicInteger();
64
65 private final Object lock = new Object();
66
67 private final IoAcceptor wrapper;
68
69 private final Executor executor;
70
71 private final int id = nextId.getAndIncrement();
72
73 private volatile Selector selector;
74
75 private DatagramAcceptorConfig defaultConfig = new DatagramAcceptorConfig();
76
77 private final Map<SocketAddress, DatagramChannel> channels = new ConcurrentHashMap<SocketAddress, DatagramChannel>();
78
79 private final Queue<RegistrationRequest> registerQueue = new ConcurrentLinkedQueue<RegistrationRequest>();
80
81 private final Queue<CancellationRequest> cancelQueue = new ConcurrentLinkedQueue<CancellationRequest>();
82
83 private final Queue<DatagramSessionImpl> flushingSessions = new ConcurrentLinkedQueue<DatagramSessionImpl>();
84
85 private Worker worker;
86
87
88
89
90 public DatagramAcceptorDelegate(IoAcceptor wrapper, Executor executor) {
91 this.wrapper = wrapper;
92 this.executor = executor;
93
94
95 defaultConfig.getSessionConfig().setReuseAddress(true);
96 }
97
98 public void bind(SocketAddress address, IoHandler handler,
99 IoServiceConfig config) throws IOException {
100 if (handler == null)
101 throw new NullPointerException("handler");
102 if (config == null) {
103 config = getDefaultConfig();
104 }
105
106 if (address != null && !(address instanceof InetSocketAddress))
107 throw new IllegalArgumentException("Unexpected address type: "
108 + address.getClass());
109
110 RegistrationRequest request = new RegistrationRequest(address, handler,
111 config);
112
113 synchronized (lock) {
114 startupWorker();
115 registerQueue.add(request);
116 selector.wakeup();
117 }
118
119 synchronized (request) {
120 while (!request.done) {
121 try {
122 request.wait();
123 } catch (InterruptedException e) {
124 throw new RuntimeIOException(e);
125 }
126 }
127 }
128
129 if (request.exception != null) {
130 throw (IOException) new IOException("Failed to bind")
131 .initCause(request.exception);
132 }
133 }
134
135 public void unbind(SocketAddress address) {
136 if (address == null)
137 throw new NullPointerException("address");
138
139 CancellationRequest request = new CancellationRequest(address);
140 synchronized (lock) {
141 try {
142 startupWorker();
143 } catch (IOException e) {
144
145
146
147
148 throw new IllegalArgumentException("Address not bound: " + address);
149 }
150
151 cancelQueue.add(request);
152 selector.wakeup();
153 }
154
155 synchronized (request) {
156 while (!request.done) {
157 try {
158 request.wait();
159 } catch (InterruptedException e) {
160 throw new RuntimeIOException(e);
161 }
162 }
163 }
164
165 if (request.exception != null) {
166 throw new RuntimeException("Failed to unbind", request.exception);
167 }
168 }
169
170 public void unbindAll() {
171 List<SocketAddress> addresses = new ArrayList<SocketAddress>(channels
172 .keySet());
173
174 for (SocketAddress address : addresses) {
175 unbind(address);
176 }
177 }
178
179 @Override
180 public IoSession newSession(SocketAddress remoteAddress,
181 SocketAddress localAddress) {
182 if (remoteAddress == null) {
183 throw new NullPointerException("remoteAddress");
184 }
185 if (localAddress == null) {
186 throw new NullPointerException("localAddress");
187 }
188
189 Selector selector = this.selector;
190 DatagramChannel ch = channels.get(localAddress);
191 if (selector == null || ch == null) {
192 throw new IllegalArgumentException("Unknown localAddress: "
193 + localAddress);
194 }
195
196 SelectionKey key = ch.keyFor(selector);
197 if (key == null) {
198 throw new IllegalArgumentException("Unknown localAddress: "
199 + localAddress);
200 }
201
202 RegistrationRequest req = (RegistrationRequest) key.attachment();
203 IoSession session;
204 IoSessionRecycler sessionRecycler = getSessionRecycler(req);
205 synchronized (sessionRecycler) {
206 session = sessionRecycler.recycle(localAddress, remoteAddress);
207 if (session != null) {
208 return session;
209 }
210
211
212
213
214
215
216
217 DatagramSessionImpl datagramSession = new DatagramSessionImpl(
218 wrapper, this, req.config, ch, req.handler, req.address,
219 req.address);
220 datagramSession.setRemoteAddress(remoteAddress);
221 datagramSession.setSelectionKey(key);
222
223 getSessionRecycler(req).put(datagramSession);
224 session = datagramSession;
225 }
226
227 try {
228 buildFilterChain(req, session);
229 getListeners().fireSessionCreated(session);
230 } catch (Throwable t) {
231 ExceptionMonitor.getInstance().exceptionCaught(t);
232 }
233
234 return session;
235 }
236
237 private IoSessionRecycler getSessionRecycler(RegistrationRequest req) {
238 IoSessionRecycler sessionRecycler;
239 if (req.config instanceof DatagramServiceConfig) {
240 sessionRecycler = ((DatagramServiceConfig) req.config)
241 .getSessionRecycler();
242 } else {
243 sessionRecycler = defaultConfig.getSessionRecycler();
244 }
245 return sessionRecycler;
246 }
247
248 @Override
249 public IoServiceListenerSupport getListeners() {
250 return super.getListeners();
251 }
252
253 private void buildFilterChain(RegistrationRequest req, IoSession session)
254 throws Exception {
255 this.getFilterChainBuilder().buildFilterChain(session.getFilterChain());
256 req.config.getFilterChainBuilder().buildFilterChain(
257 session.getFilterChain());
258 req.config.getThreadModel().buildFilterChain(session.getFilterChain());
259 }
260
261 public DatagramAcceptorConfig getDefaultConfig() {
262 return defaultConfig;
263 }
264
265
266
267
268
269
270
271 public void setDefaultConfig(DatagramAcceptorConfig defaultConfig) {
272 if (defaultConfig == null) {
273 throw new NullPointerException("defaultConfig");
274 }
275 this.defaultConfig = defaultConfig;
276 }
277
278 private void startupWorker() throws IOException {
279 synchronized (lock) {
280 if (worker == null) {
281 selector = Selector.open();
282 worker = new Worker();
283 executor.execute(
284 new NamePreservingRunnable(worker, "DatagramAcceptor-" + id));
285 }
286 }
287 }
288
289 public void flushSession(DatagramSessionImpl session) {
290 if (scheduleFlush(session)) {
291 Selector selector = this.selector;
292 if (selector != null) {
293 selector.wakeup();
294 }
295 }
296 }
297
298 public void closeSession(DatagramSessionImpl session) {
299 }
300
301 private boolean scheduleFlush(DatagramSessionImpl session) {
302 if (session.setScheduledForFlush(true)) {
303 flushingSessions.add(session);
304 return true;
305 } else {
306 return false;
307 }
308 }
309
310 private class Worker implements Runnable {
311 public void run() {
312 Selector selector = DatagramAcceptorDelegate.this.selector;
313 for (;;) {
314 try {
315 int nKeys = selector.select();
316
317 registerNew();
318
319 if (nKeys > 0) {
320 processReadySessions(selector.selectedKeys());
321 }
322
323 flushSessions();
324 cancelKeys();
325
326 if (selector.keys().isEmpty()) {
327 synchronized (lock) {
328 if (selector.keys().isEmpty()
329 && registerQueue.isEmpty()
330 && cancelQueue.isEmpty()) {
331 worker = null;
332 try {
333 selector.close();
334 } catch (IOException e) {
335 ExceptionMonitor.getInstance()
336 .exceptionCaught(e);
337 } finally {
338 DatagramAcceptorDelegate.this.selector = null;
339 }
340 break;
341 }
342 }
343 }
344 } catch (IOException e) {
345 ExceptionMonitor.getInstance().exceptionCaught(e);
346
347 try {
348 Thread.sleep(1000);
349 } catch (InterruptedException e1) {
350 ExceptionMonitor.getInstance().exceptionCaught(e1);
351 }
352 }
353 }
354 }
355 }
356
357 private void processReadySessions(Set<SelectionKey> keys) {
358 Iterator<SelectionKey> it = keys.iterator();
359 while (it.hasNext()) {
360 SelectionKey key = it.next();
361 it.remove();
362
363 DatagramChannel ch = (DatagramChannel) key.channel();
364
365 RegistrationRequest req = (RegistrationRequest) key.attachment();
366 try {
367 if (key.isReadable()) {
368 readSession(ch, req);
369 }
370
371 if (key.isWritable()) {
372 for (Object o : getManagedSessions(req.address)) {
373 scheduleFlush((DatagramSessionImpl) o);
374 }
375 }
376 } catch (Throwable t) {
377 ExceptionMonitor.getInstance().exceptionCaught(t);
378 }
379 }
380 }
381
382 private void readSession(DatagramChannel channel, RegistrationRequest req)
383 throws Exception {
384 ByteBuffer readBuf = ByteBuffer
385 .allocate(((DatagramSessionConfig) req.config
386 .getSessionConfig()).getReceiveBufferSize());
387 try {
388 SocketAddress remoteAddress = channel.receive(readBuf.buf());
389 if (remoteAddress != null) {
390 DatagramSessionImpl session = (DatagramSessionImpl) newSession(
391 remoteAddress, req.address);
392
393 readBuf.flip();
394
395 ByteBuffer newBuf = ByteBuffer.allocate(readBuf.limit());
396 newBuf.put(readBuf);
397 newBuf.flip();
398
399 session.increaseReadBytes(newBuf.remaining());
400 session.getFilterChain().fireMessageReceived(session, newBuf);
401 }
402 } finally {
403 readBuf.release();
404 }
405 }
406
407 private void flushSessions() {
408 if (flushingSessions.size() == 0)
409 return;
410
411 for (;;) {
412 DatagramSessionImpl session = flushingSessions.poll();
413
414 if (session == null)
415 break;
416
417 session.setScheduledForFlush(false);
418
419 try {
420 boolean flushedAll = flush(session);
421 if (flushedAll && !session.getWriteRequestQueue().isEmpty() && !session.isScheduledForFlush()) {
422 scheduleFlush(session);
423 }
424 } catch (IOException e) {
425 session.getFilterChain().fireExceptionCaught(session, e);
426 }
427 }
428 }
429
430 private boolean flush(DatagramSessionImpl session) throws IOException {
431
432 SelectionKey key = session.getSelectionKey();
433 if (key == null) {
434 scheduleFlush(session);
435 return false;
436 }
437 if (!key.isValid()) {
438 return false;
439 }
440 key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
441
442 DatagramChannel ch = session.getChannel();
443 Queue<WriteRequest> writeRequestQueue = session.getWriteRequestQueue();
444
445 int writtenBytes = 0;
446 int maxWrittenBytes = ((DatagramSessionConfig) session.getConfig()).getSendBufferSize() << 1;
447 try {
448 for (;;) {
449 WriteRequest req = writeRequestQueue.peek();
450
451 if (req == null)
452 break;
453
454 ByteBuffer buf = (ByteBuffer) req.getMessage();
455 if (buf.remaining() == 0) {
456
457 writeRequestQueue.poll();
458
459 buf.reset();
460
461 if (!buf.hasRemaining()) {
462 session.increaseWrittenMessages();
463 }
464 session.getFilterChain().fireMessageSent(session, req);
465 continue;
466 }
467
468 SocketAddress destination = req.getDestination();
469 if (destination == null) {
470 destination = session.getRemoteAddress();
471 }
472
473 int localWrittenBytes = ch.send(buf.buf(), destination);
474 writtenBytes += localWrittenBytes;
475
476 if (localWrittenBytes == 0 || writtenBytes >= maxWrittenBytes) {
477
478 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
479 return false;
480 } else {
481
482 writeRequestQueue.poll();
483
484 buf.reset();
485
486 if (!buf.hasRemaining()) {
487 session.increaseWrittenMessages();
488 }
489
490 session.getFilterChain().fireMessageSent(session, req);
491 }
492 }
493 } finally {
494 session.increaseWrittenBytes(writtenBytes);
495 }
496
497 return true;
498 }
499
500 private void registerNew() {
501 if (registerQueue.isEmpty())
502 return;
503
504 Selector selector = this.selector;
505 for (;;) {
506 RegistrationRequest req = registerQueue.poll();
507
508 if (req == null)
509 break;
510
511 DatagramChannel ch = null;
512 try {
513 ch = DatagramChannel.open();
514 DatagramSessionConfig cfg;
515 if (req.config.getSessionConfig() instanceof DatagramSessionConfig) {
516 cfg = (DatagramSessionConfig) req.config.getSessionConfig();
517 } else {
518 cfg = getDefaultConfig().getSessionConfig();
519 }
520
521 ch.socket().setReuseAddress(cfg.isReuseAddress());
522 ch.socket().setBroadcast(cfg.isBroadcast());
523 ch.socket().setReceiveBufferSize(cfg.getReceiveBufferSize());
524 ch.socket().setSendBufferSize(cfg.getSendBufferSize());
525
526 if (ch.socket().getTrafficClass() != cfg.getTrafficClass()) {
527 ch.socket().setTrafficClass(cfg.getTrafficClass());
528 }
529
530 ch.configureBlocking(false);
531 ch.socket().bind(req.address);
532 if (req.address == null || req.address.getPort() == 0) {
533 req.address = (InetSocketAddress) ch.socket()
534 .getLocalSocketAddress();
535 }
536 ch.register(selector, SelectionKey.OP_READ, req);
537 channels.put(req.address, ch);
538
539 getListeners().fireServiceActivated(this, req.address,
540 req.handler, req.config);
541 } catch (Throwable t) {
542 req.exception = t;
543 } finally {
544 synchronized (req) {
545 req.done = true;
546 req.notify();
547 }
548
549 if (ch != null && req.exception != null) {
550 try {
551 ch.disconnect();
552 ch.close();
553 } catch (Throwable e) {
554 ExceptionMonitor.getInstance().exceptionCaught(e);
555 }
556 }
557 }
558 }
559 }
560
561 private void cancelKeys() {
562 if (cancelQueue.isEmpty())
563 return;
564
565 Selector selector = this.selector;
566 for (;;) {
567 CancellationRequest request = cancelQueue.poll();
568
569 if (request == null) {
570 break;
571 }
572
573 DatagramChannel ch = channels.remove(request.address);
574
575
576 try {
577 if (ch == null) {
578 request.exception = new IllegalArgumentException(
579 "Address not bound: " + request.address);
580 } else {
581 SelectionKey key = ch.keyFor(selector);
582 request.registrationRequest = (RegistrationRequest) key
583 .attachment();
584 key.cancel();
585 selector.wakeup();
586 ch.disconnect();
587 ch.close();
588 }
589 } catch (Throwable t) {
590 ExceptionMonitor.getInstance().exceptionCaught(t);
591 } finally {
592 synchronized (request) {
593 request.done = true;
594 request.notify();
595 }
596
597 if (request.exception == null) {
598 getListeners().fireServiceDeactivated(this,
599 request.address,
600 request.registrationRequest.handler,
601 request.registrationRequest.config);
602 }
603 }
604 }
605 }
606
607 public void updateTrafficMask(DatagramSessionImpl session) {
608
609
610
611 }
612
613 private static class RegistrationRequest {
614 private InetSocketAddress address;
615
616 private final IoHandler handler;
617
618 private final IoServiceConfig config;
619
620 private Throwable exception;
621
622 private boolean done;
623
624 private RegistrationRequest(SocketAddress address, IoHandler handler,
625 IoServiceConfig config) {
626 this.address = (InetSocketAddress) address;
627 this.handler = handler;
628 this.config = config;
629 }
630 }
631
632 private static class CancellationRequest {
633 private final SocketAddress address;
634
635 private boolean done;
636
637 private RegistrationRequest registrationRequest;
638
639 private RuntimeException exception;
640
641 private CancellationRequest(SocketAddress address) {
642 this.address = address;
643 }
644 }
645 }