001 /**
002 * Copyright (C) 2012 FuseSource, Inc.
003 * http://fusesource.com
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.fusesource.hawtdispatch.transport;
018
019 import org.fusesource.hawtdispatch.*;
020
021 import java.io.IOException;
022 import java.net.*;
023 import java.nio.channels.DatagramChannel;
024 import java.nio.channels.ReadableByteChannel;
025 import java.nio.channels.SelectionKey;
026 import java.nio.channels.WritableByteChannel;
027 import java.util.LinkedList;
028
029 /**
030 * <p>
031 * </p>
032 *
033 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
034 */
035 public class UdpTransport extends ServiceBase implements Transport {
036
037 public static final SocketAddress ANY_ADDRESS = new SocketAddress() {
038 @Override
039 public String toString() {
040 return "*:*";
041 }
042 };
043
044
045 abstract static class SocketState {
046 void onStop(Task onCompleted) {
047 }
048 void onCanceled() {
049 }
050 boolean is(Class<? extends SocketState> clazz) {
051 return getClass()==clazz;
052 }
053 }
054
055 static class DISCONNECTED extends SocketState{}
056
057 class CONNECTED extends SocketState {
058
059 public CONNECTED() {
060 localAddress = channel.socket().getLocalSocketAddress();
061 remoteAddress = channel.socket().getRemoteSocketAddress();
062 if(remoteAddress == null ) {
063 remoteAddress = ANY_ADDRESS;
064 }
065 }
066
067 void onStop(Task onCompleted) {
068 trace("CONNECTED.onStop");
069 CANCELING state = new CANCELING();
070 socketState = state;
071 state.add(createDisconnectTask());
072 state.onStop(onCompleted);
073 }
074 void onCanceled() {
075 trace("CONNECTED.onCanceled");
076 CANCELING state = new CANCELING();
077 socketState = state;
078 state.add(createDisconnectTask());
079 state.onCanceled();
080 }
081 Task createDisconnectTask() {
082 return new Task(){
083 public void run() {
084 listener.onTransportDisconnected();
085 }
086 };
087 }
088 }
089
090 class CANCELING extends SocketState {
091 private LinkedList<Task> runnables = new LinkedList<Task>();
092 private int remaining;
093 private boolean dispose;
094
095 public CANCELING() {
096 if( readSource!=null ) {
097 remaining++;
098 readSource.cancel();
099 }
100 if( writeSource!=null ) {
101 remaining++;
102 writeSource.cancel();
103 }
104 }
105 void onStop(Task onCompleted) {
106 trace("CANCELING.onCompleted");
107 add(onCompleted);
108 dispose = true;
109 }
110 void add(Task onCompleted) {
111 if( onCompleted!=null ) {
112 runnables.add(onCompleted);
113 }
114 }
115 void onCanceled() {
116 trace("CANCELING.onCanceled");
117 remaining--;
118 if( remaining!=0 ) {
119 return;
120 }
121 try {
122 channel.close();
123 } catch (IOException ignore) {
124 }
125 socketState = new CANCELED(dispose);
126 for (Task runnable : runnables) {
127 runnable.run();
128 }
129 if (dispose) {
130 dispose();
131 }
132 }
133 }
134
135 class CANCELED extends SocketState {
136 private boolean disposed;
137
138 public CANCELED(boolean disposed) {
139 this.disposed=disposed;
140 }
141
142 void onStop(Task onCompleted) {
143 trace("CANCELED.onStop");
144 if( !disposed ) {
145 disposed = true;
146 dispose();
147 }
148 onCompleted.run();
149 }
150 }
151
152 protected URI remoteLocation;
153 protected URI localLocation;
154 protected TransportListener listener;
155 protected ProtocolCodec codec;
156
157 protected DatagramChannel channel;
158
159 protected SocketState socketState = new DISCONNECTED();
160
161 protected DispatchQueue dispatchQueue;
162 private DispatchSource readSource;
163 private DispatchSource writeSource;
164 protected CustomDispatchSource<Integer, Integer> drainOutboundSource;
165 protected CustomDispatchSource<Integer, Integer> yieldSource;
166
167 protected boolean useLocalHost = true;
168
169 int receiveBufferSize = 1024*64;
170 int sendBufferSize = 1024*64;
171
172
173 public static final int IPTOS_LOWCOST = 0x02;
174 public static final int IPTOS_RELIABILITY = 0x04;
175 public static final int IPTOS_THROUGHPUT = 0x08;
176 public static final int IPTOS_LOWDELAY = 0x10;
177
178 int trafficClass = IPTOS_THROUGHPUT;
179
180 SocketAddress localAddress;
181 SocketAddress remoteAddress = ANY_ADDRESS;
182
183
184 private final Task CANCEL_HANDLER = new Task() {
185 public void run() {
186 socketState.onCanceled();
187 }
188 };
189
190 static final class OneWay {
191 final Object command;
192 final Retained retained;
193
194 public OneWay(Object command, Retained retained) {
195 this.command = command;
196 this.retained = retained;
197 }
198 }
199
200 public void connected(DatagramChannel channel) throws IOException, Exception {
201 this.channel = channel;
202 initializeChannel();
203 this.socketState = new CONNECTED();
204 }
205
206 protected void initializeChannel() throws Exception {
207 this.channel.configureBlocking(false);
208 DatagramSocket socket = channel.socket();
209 try {
210 socket.setReuseAddress(true);
211 } catch (SocketException e) {
212 }
213 try {
214 socket.setTrafficClass(trafficClass);
215 } catch (SocketException e) {
216 }
217 try {
218 socket.setReceiveBufferSize(receiveBufferSize);
219 } catch (SocketException e) {
220 }
221 try {
222 socket.setSendBufferSize(sendBufferSize);
223 } catch (SocketException e) {
224 }
225 if( channel!=null && codec!=null ) {
226 initializeCodec();
227 }
228 }
229
230 protected void initializeCodec() throws Exception {
231 codec.setReadableByteChannel(readChannel());
232 codec.setWritableByteChannel(writeChannel());
233 }
234
235 public void connecting(URI remoteLocation, URI localLocation) throws IOException, Exception {
236 this.channel = DatagramChannel.open();
237 initializeChannel();
238 this.remoteLocation = remoteLocation;
239 this.localLocation = localLocation;
240
241 if (localLocation != null) {
242 InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(localLocation.getHost()), localLocation.getPort());
243 channel.socket().bind(localAddress);
244 }
245
246 String host = resolveHostName(remoteLocation.getHost());
247 InetSocketAddress remoteAddress = new InetSocketAddress(host, remoteLocation.getPort());
248 channel.connect(remoteAddress);
249 this.socketState = new CONNECTED();
250 }
251
252
253 public DispatchQueue getDispatchQueue() {
254 return dispatchQueue;
255 }
256
257 public void setDispatchQueue(DispatchQueue queue) {
258 this.dispatchQueue = queue;
259 if(readSource!=null) readSource.setTargetQueue(queue);
260 if(writeSource!=null) writeSource.setTargetQueue(queue);
261 if(drainOutboundSource!=null) drainOutboundSource.setTargetQueue(queue);
262 if(yieldSource!=null) yieldSource.setTargetQueue(queue);
263 }
264
265 public void _start(Task onCompleted) {
266 try {
267 if (socketState.is(CONNECTED.class) ) {
268 dispatchQueue.execute(new Task() {
269 public void run() {
270 try {
271 trace("was connected.");
272 onConnected();
273 } catch (IOException e) {
274 onTransportFailure(e);
275 }
276 }
277 });
278 } else {
279 System.err.println("cannot be started. socket state is: "+socketState);
280 }
281 } finally {
282 if( onCompleted!=null ) {
283 onCompleted.run();
284 }
285 }
286 }
287
288 public void _stop(final Task onCompleted) {
289 trace("stopping.. at state: "+socketState);
290 socketState.onStop(onCompleted);
291 }
292
293 protected String resolveHostName(String host) throws UnknownHostException {
294 String localName = InetAddress.getLocalHost().getHostName();
295 if (localName != null && isUseLocalHost()) {
296 if (localName.equals(host)) {
297 return "localhost";
298 }
299 }
300 return host;
301 }
302
303 protected void onConnected() throws IOException {
304 yieldSource = Dispatch.createSource(EventAggregators.INTEGER_ADD, dispatchQueue);
305 yieldSource.setEventHandler(new Task() {
306 public void run() {
307 drainInbound();
308 }
309 });
310 yieldSource.resume();
311 drainOutboundSource = Dispatch.createSource(EventAggregators.INTEGER_ADD, dispatchQueue);
312 drainOutboundSource.setEventHandler(new Task() {
313 public void run() {
314 flush();
315 }
316 });
317 drainOutboundSource.resume();
318
319 readSource = Dispatch.createSource(channel, SelectionKey.OP_READ, dispatchQueue);
320 writeSource = Dispatch.createSource(channel, SelectionKey.OP_WRITE, dispatchQueue);
321
322 readSource.setCancelHandler(CANCEL_HANDLER);
323 writeSource.setCancelHandler(CANCEL_HANDLER);
324
325 readSource.setEventHandler(new Task() {
326 public void run() {
327 drainInbound();
328 }
329 });
330 writeSource.setEventHandler(new Task() {
331 public void run() {
332 flush();
333 }
334 });
335 listener.onTransportConnected();
336 }
337
338 Task onDispose;
339
340 private void dispose() {
341 if( readSource!=null ) {
342 readSource.cancel();
343 readSource=null;
344 }
345
346 if( writeSource!=null ) {
347 writeSource.cancel();
348 writeSource=null;
349 }
350 this.codec = null;
351 if(onDispose!=null) {
352 onDispose.run();
353 onDispose = null;
354 }
355 }
356
357 public void onTransportFailure(IOException error) {
358 listener.onTransportFailure(error);
359 socketState.onCanceled();
360 }
361
362
363 public boolean full() {
364 return codec==null || codec.full();
365 }
366
367 boolean rejectingOffers;
368
369 public boolean offer(Object command) {
370 dispatchQueue.assertExecuting();
371 try {
372 if (!socketState.is(CONNECTED.class)) {
373 throw new IOException("Not connected.");
374 }
375 if (getServiceState() != STARTED) {
376 throw new IOException("Not running.");
377 }
378
379 ProtocolCodec.BufferState rc = codec.write(command);
380 rejectingOffers = codec.full();
381 switch (rc ) {
382 case FULL:
383 return false;
384 default:
385 drainOutboundSource.merge(1);
386 return true;
387 }
388 } catch (IOException e) {
389 onTransportFailure(e);
390 return false;
391 }
392
393 }
394
395 boolean writeResumedForCodecFlush = false;
396
397 /**
398 *
399 */
400 public void flush() {
401 dispatchQueue.assertExecuting();
402 if (getServiceState() != STARTED || !socketState.is(CONNECTED.class)) {
403 return;
404 }
405 try {
406 if( codec.flush() == ProtocolCodec.BufferState.EMPTY && transportFlush() ) {
407 if( writeResumedForCodecFlush) {
408 writeResumedForCodecFlush = false;
409 suspendWrite();
410 }
411 rejectingOffers = false;
412 listener.onRefill();
413
414 } else {
415 if(!writeResumedForCodecFlush) {
416 writeResumedForCodecFlush = true;
417 resumeWrite();
418 }
419 }
420 } catch (IOException e) {
421 onTransportFailure(e);
422 }
423 }
424
425 protected boolean transportFlush() throws IOException {
426 return true;
427 }
428
429 protected void drainInbound() {
430 if (!getServiceState().isStarted() || readSource.isSuspended()) {
431 return;
432 }
433 try {
434 long initial = codec.getReadCounter();
435 // Only process upto 2 x the read buffer worth of data at a time so we can give
436 // other connections a chance to process their requests.
437 while( codec.getReadCounter()-initial < codec.getReadBufferSize()<<2 ) {
438 Object command = codec.read();
439 if ( command!=null ) {
440 try {
441 listener.onTransportCommand(command);
442 } catch (Throwable e) {
443 e.printStackTrace();
444 onTransportFailure(new IOException("Transport listener failure."));
445 }
446
447 // the transport may be suspended after processing a command.
448 if (getServiceState() == STOPPED || readSource.isSuspended()) {
449 return;
450 }
451 } else {
452 return;
453 }
454 }
455 yieldSource.merge(1);
456 } catch (IOException e) {
457 onTransportFailure(e);
458 }
459 }
460
461 public SocketAddress getLocalAddress() {
462 return localAddress;
463 }
464
465 public SocketAddress getRemoteAddress() {
466 return remoteAddress;
467 }
468
469 private boolean assertConnected() {
470 try {
471 if ( !isConnected() ) {
472 throw new IOException("Not connected.");
473 }
474 return true;
475 } catch (IOException e) {
476 onTransportFailure(e);
477 }
478 return false;
479 }
480
481 public void suspendRead() {
482 if( isConnected() && readSource!=null ) {
483 readSource.suspend();
484 }
485 }
486
487
488 public void resumeRead() {
489 if( isConnected() && readSource!=null ) {
490 _resumeRead();
491 }
492 }
493
494 private void _resumeRead() {
495 readSource.resume();
496 dispatchQueue.execute(new Task(){
497 public void run() {
498 drainInbound();
499 }
500 });
501 }
502
503 protected void suspendWrite() {
504 if( isConnected() && writeSource!=null ) {
505 writeSource.suspend();
506 }
507 }
508
509 protected void resumeWrite() {
510 if( isConnected() && writeSource!=null ) {
511 writeSource.resume();
512 }
513 }
514
515 public TransportListener getTransportListener() {
516 return listener;
517 }
518
519 public void setTransportListener(TransportListener transportListener) {
520 this.listener = transportListener;
521 }
522
523 public ProtocolCodec getProtocolCodec() {
524 return codec;
525 }
526
527 public void setProtocolCodec(ProtocolCodec protocolCodec) throws Exception {
528 this.codec = protocolCodec;
529 if( channel!=null && codec!=null ) {
530 initializeCodec();
531 }
532 }
533
534 public boolean isConnected() {
535 return socketState.is(CONNECTED.class);
536 }
537
538 public boolean isClosed() {
539 return getServiceState() == STOPPED;
540 }
541
542 public boolean isUseLocalHost() {
543 return useLocalHost;
544 }
545
546 /**
547 * Sets whether 'localhost' or the actual local host name should be used to
548 * make local connections. On some operating systems such as Macs its not
549 * possible to connect as the local host name so localhost is better.
550 */
551 public void setUseLocalHost(boolean useLocalHost) {
552 this.useLocalHost = useLocalHost;
553 }
554
555 private void trace(String message) {
556 // TODO:
557 }
558
559 public DatagramChannel getDatagramChannel() {
560 return channel;
561 }
562
563 public ReadableByteChannel readChannel() {
564 return channel;
565 }
566
567 public WritableByteChannel writeChannel() {
568 return channel;
569 }
570
571 public int getTrafficClass() {
572 return trafficClass;
573 }
574
575 public void setTrafficClass(int trafficClass) {
576 this.trafficClass = trafficClass;
577 }
578
579 public int getReceiveBufferSize() {
580 return receiveBufferSize;
581 }
582
583 public void setReceiveBufferSize(int receiveBufferSize) {
584 this.receiveBufferSize = receiveBufferSize;
585 }
586
587 public int getSendBufferSize() {
588 return sendBufferSize;
589 }
590
591 public void setSendBufferSize(int sendBufferSize) {
592 this.sendBufferSize = sendBufferSize;
593 }
594
595 }