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(new NamePreservingRunnable(worker));
284 }
285 }
286 }
287
288 public void flushSession(DatagramSessionImpl session) {
289 if (scheduleFlush(session)) {
290 Selector selector = this.selector;
291 if (selector != null) {
292 selector.wakeup();
293 }
294 }
295 }
296
297 public void closeSession(DatagramSessionImpl session) {
298 }
299
300 private boolean scheduleFlush(DatagramSessionImpl session) {
301 if (session.setScheduledForFlush(true)) {
302 flushingSessions.add(session);
303 return true;
304 } else {
305 return false;
306 }
307 }
308
309 private class Worker implements Runnable {
310 public void run() {
311 Thread.currentThread().setName("DatagramAcceptor-" + id);
312
313 Selector selector = DatagramAcceptorDelegate.this.selector;
314 for (;;) {
315 try {
316 int nKeys = selector.select();
317
318 registerNew();
319
320 if (nKeys > 0) {
321 processReadySessions(selector.selectedKeys());
322 }
323
324 flushSessions();
325 cancelKeys();
326
327 if (selector.keys().isEmpty()) {
328 synchronized (lock) {
329 if (selector.keys().isEmpty()
330 && registerQueue.isEmpty()
331 && cancelQueue.isEmpty()) {
332 worker = null;
333 try {
334 selector.close();
335 } catch (IOException e) {
336 ExceptionMonitor.getInstance()
337 .exceptionCaught(e);
338 } finally {
339 DatagramAcceptorDelegate.this.selector = null;
340 }
341 break;
342 }
343 }
344 }
345 } catch (IOException e) {
346 ExceptionMonitor.getInstance().exceptionCaught(e);
347
348 try {
349 Thread.sleep(1000);
350 } catch (InterruptedException e1) {
351 ExceptionMonitor.getInstance().exceptionCaught(e1);
352 }
353 }
354 }
355 }
356 }
357
358 private void processReadySessions(Set<SelectionKey> keys) {
359 Iterator<SelectionKey> it = keys.iterator();
360 while (it.hasNext()) {
361 SelectionKey key = it.next();
362 it.remove();
363
364 DatagramChannel ch = (DatagramChannel) key.channel();
365
366 RegistrationRequest req = (RegistrationRequest) key.attachment();
367 try {
368 if (key.isReadable()) {
369 readSession(ch, req);
370 }
371
372 if (key.isWritable()) {
373 for (Object o : getManagedSessions(req.address)) {
374 scheduleFlush((DatagramSessionImpl) o);
375 }
376 }
377 } catch (Throwable t) {
378 ExceptionMonitor.getInstance().exceptionCaught(t);
379 }
380 }
381 }
382
383 private void readSession(DatagramChannel channel, RegistrationRequest req)
384 throws Exception {
385 ByteBuffer readBuf = ByteBuffer
386 .allocate(((DatagramSessionConfig) req.config
387 .getSessionConfig()).getReceiveBufferSize());
388 try {
389 SocketAddress remoteAddress = channel.receive(readBuf.buf());
390 if (remoteAddress != null) {
391 DatagramSessionImpl session = (DatagramSessionImpl) newSession(
392 remoteAddress, req.address);
393
394 readBuf.flip();
395
396 ByteBuffer newBuf = ByteBuffer.allocate(readBuf.limit());
397 newBuf.put(readBuf);
398 newBuf.flip();
399
400 session.increaseReadBytes(newBuf.remaining());
401 session.getFilterChain().fireMessageReceived(session, newBuf);
402 }
403 } finally {
404 readBuf.release();
405 }
406 }
407
408 private void flushSessions() {
409 if (flushingSessions.size() == 0)
410 return;
411
412 for (;;) {
413 DatagramSessionImpl session = flushingSessions.poll();
414
415 if (session == null)
416 break;
417
418 session.setScheduledForFlush(false);
419
420 try {
421 boolean flushedAll = flush(session);
422 if (flushedAll && !session.getWriteRequestQueue().isEmpty() && !session.isScheduledForFlush()) {
423 scheduleFlush(session);
424 }
425 } catch (IOException e) {
426 session.getFilterChain().fireExceptionCaught(session, e);
427 }
428 }
429 }
430
431 private boolean flush(DatagramSessionImpl session) throws IOException {
432
433 SelectionKey key = session.getSelectionKey();
434 if (key == null) {
435 scheduleFlush(session);
436 return false;
437 }
438 if (!key.isValid()) {
439 return false;
440 }
441 key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
442
443 DatagramChannel ch = session.getChannel();
444 Queue<WriteRequest> writeRequestQueue = session.getWriteRequestQueue();
445
446 int writtenBytes = 0;
447 int maxWrittenBytes = ((DatagramSessionConfig) session.getConfig()).getSendBufferSize() << 1;
448 try {
449 for (;;) {
450 WriteRequest req = writeRequestQueue.peek();
451
452 if (req == null)
453 break;
454
455 ByteBuffer buf = (ByteBuffer) req.getMessage();
456 if (buf.remaining() == 0) {
457
458 writeRequestQueue.poll();
459
460 session.increaseWrittenMessages();
461 buf.reset();
462 session.getFilterChain().fireMessageSent(session, req);
463 continue;
464 }
465
466 SocketAddress destination = req.getDestination();
467 if (destination == null) {
468 destination = session.getRemoteAddress();
469 }
470
471 int localWrittenBytes = ch.send(buf.buf(), destination);
472 writtenBytes += localWrittenBytes;
473
474 if (localWrittenBytes == 0 || writtenBytes >= maxWrittenBytes) {
475
476 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
477 return false;
478 } else {
479
480 writeRequestQueue.poll();
481
482 session.increaseWrittenMessages();
483 buf.reset();
484 session.getFilterChain().fireMessageSent(session, req);
485 }
486 }
487 } finally {
488 session.increaseWrittenBytes(writtenBytes);
489 }
490
491 return true;
492 }
493
494 private void registerNew() {
495 if (registerQueue.isEmpty())
496 return;
497
498 Selector selector = this.selector;
499 for (;;) {
500 RegistrationRequest req = registerQueue.poll();
501
502 if (req == null)
503 break;
504
505 DatagramChannel ch = null;
506 try {
507 ch = DatagramChannel.open();
508 DatagramSessionConfig cfg;
509 if (req.config.getSessionConfig() instanceof DatagramSessionConfig) {
510 cfg = (DatagramSessionConfig) req.config.getSessionConfig();
511 } else {
512 cfg = getDefaultConfig().getSessionConfig();
513 }
514
515 ch.socket().setReuseAddress(cfg.isReuseAddress());
516 ch.socket().setBroadcast(cfg.isBroadcast());
517 ch.socket().setReceiveBufferSize(cfg.getReceiveBufferSize());
518 ch.socket().setSendBufferSize(cfg.getSendBufferSize());
519
520 if (ch.socket().getTrafficClass() != cfg.getTrafficClass()) {
521 ch.socket().setTrafficClass(cfg.getTrafficClass());
522 }
523
524 ch.configureBlocking(false);
525 ch.socket().bind(req.address);
526 if (req.address == null || req.address.getPort() == 0) {
527 req.address = (InetSocketAddress) ch.socket()
528 .getLocalSocketAddress();
529 }
530 ch.register(selector, SelectionKey.OP_READ, req);
531 channels.put(req.address, ch);
532
533 getListeners().fireServiceActivated(this, req.address,
534 req.handler, req.config);
535 } catch (Throwable t) {
536 req.exception = t;
537 } finally {
538 synchronized (req) {
539 req.done = true;
540 req.notify();
541 }
542
543 if (ch != null && req.exception != null) {
544 try {
545 ch.disconnect();
546 ch.close();
547 } catch (Throwable e) {
548 ExceptionMonitor.getInstance().exceptionCaught(e);
549 }
550 }
551 }
552 }
553 }
554
555 private void cancelKeys() {
556 if (cancelQueue.isEmpty())
557 return;
558
559 Selector selector = this.selector;
560 for (;;) {
561 CancellationRequest request = cancelQueue.poll();
562
563 if (request == null) {
564 break;
565 }
566
567 DatagramChannel ch = channels.remove(request.address);
568
569
570 try {
571 if (ch == null) {
572 request.exception = new IllegalArgumentException(
573 "Address not bound: " + request.address);
574 } else {
575 SelectionKey key = ch.keyFor(selector);
576 request.registrationRequest = (RegistrationRequest) key
577 .attachment();
578 key.cancel();
579 selector.wakeup();
580 ch.disconnect();
581 ch.close();
582 }
583 } catch (Throwable t) {
584 ExceptionMonitor.getInstance().exceptionCaught(t);
585 } finally {
586 synchronized (request) {
587 request.done = true;
588 request.notify();
589 }
590
591 if (request.exception == null) {
592 getListeners().fireServiceDeactivated(this,
593 request.address,
594 request.registrationRequest.handler,
595 request.registrationRequest.config);
596 }
597 }
598 }
599 }
600
601 public void updateTrafficMask(DatagramSessionImpl session) {
602
603
604
605 }
606
607 private static class RegistrationRequest {
608 private InetSocketAddress address;
609
610 private final IoHandler handler;
611
612 private final IoServiceConfig config;
613
614 private Throwable exception;
615
616 private boolean done;
617
618 private RegistrationRequest(SocketAddress address, IoHandler handler,
619 IoServiceConfig config) {
620 this.address = (InetSocketAddress) address;
621 this.handler = handler;
622 this.config = config;
623 }
624 }
625
626 private static class CancellationRequest {
627 private final SocketAddress address;
628
629 private boolean done;
630
631 private RegistrationRequest registrationRequest;
632
633 private RuntimeException exception;
634
635 private CancellationRequest(SocketAddress address) {
636 this.address = address;
637 }
638 }
639 }