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