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
018 package org.fusesource.hawtdispatch.transport;
019
020 import org.fusesource.hawtdispatch.*;
021
022 import java.io.IOException;
023 import java.net.*;
024 import java.nio.ByteBuffer;
025 import java.nio.channels.ReadableByteChannel;
026 import java.nio.channels.SelectionKey;
027 import java.nio.channels.SocketChannel;
028 import java.nio.channels.WritableByteChannel;
029 import java.util.LinkedList;
030 import java.util.concurrent.TimeUnit;
031
032 /**
033 * An implementation of the {@link org.fusesource.hawtdispatch.transport.Transport} interface using raw tcp/ip
034 *
035 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
036 */
037 public class TcpTransport extends ServiceBase implements Transport {
038
039 abstract static class SocketState {
040 void onStop(Task onCompleted) {
041 }
042 void onCanceled() {
043 }
044 boolean is(Class<? extends SocketState> clazz) {
045 return getClass()==clazz;
046 }
047 }
048
049 static class DISCONNECTED extends SocketState{}
050
051 class CONNECTING extends SocketState{
052 void onStop(Task onCompleted) {
053 trace("CONNECTING.onStop");
054 CANCELING state = new CANCELING();
055 socketState = state;
056 state.onStop(onCompleted);
057 }
058 void onCanceled() {
059 trace("CONNECTING.onCanceled");
060 CANCELING state = new CANCELING();
061 socketState = state;
062 state.onCanceled();
063 }
064 }
065
066 class CONNECTED extends SocketState {
067
068 public CONNECTED() {
069 localAddress = channel.socket().getLocalSocketAddress();
070 remoteAddress = channel.socket().getRemoteSocketAddress();
071 }
072
073 void onStop(Task onCompleted) {
074 trace("CONNECTED.onStop");
075 CANCELING state = new CANCELING();
076 socketState = state;
077 state.add(createDisconnectTask());
078 state.onStop(onCompleted);
079 }
080 void onCanceled() {
081 trace("CONNECTED.onCanceled");
082 CANCELING state = new CANCELING();
083 socketState = state;
084 state.add(createDisconnectTask());
085 state.onCanceled();
086 }
087 Task createDisconnectTask() {
088 return new Task(){
089 public void run() {
090 listener.onTransportDisconnected();
091 }
092 };
093 }
094 }
095
096 class CANCELING extends SocketState {
097 private LinkedList<Task> runnables = new LinkedList<Task>();
098 private int remaining;
099 private boolean dispose;
100
101 public CANCELING() {
102 if( readSource!=null ) {
103 remaining++;
104 readSource.cancel();
105 }
106 if( writeSource!=null ) {
107 remaining++;
108 writeSource.cancel();
109 }
110 }
111 void onStop(Task onCompleted) {
112 trace("CANCELING.onCompleted");
113 add(onCompleted);
114 dispose = true;
115 }
116 void add(Task onCompleted) {
117 if( onCompleted!=null ) {
118 runnables.add(onCompleted);
119 }
120 }
121 void onCanceled() {
122 trace("CANCELING.onCanceled");
123 remaining--;
124 if( remaining!=0 ) {
125 return;
126 }
127 try {
128 channel.close();
129 } catch (IOException ignore) {
130 }
131 socketState = new CANCELED(dispose);
132 for (Task runnable : runnables) {
133 runnable.run();
134 }
135 if (dispose) {
136 dispose();
137 }
138 }
139 }
140
141 class CANCELED extends SocketState {
142 private boolean disposed;
143
144 public CANCELED(boolean disposed) {
145 this.disposed=disposed;
146 }
147
148 void onStop(Task onCompleted) {
149 trace("CANCELED.onStop");
150 if( !disposed ) {
151 disposed = true;
152 dispose();
153 }
154 onCompleted.run();
155 }
156 }
157
158 protected URI remoteLocation;
159 protected URI localLocation;
160 protected TransportListener listener;
161 protected ProtocolCodec codec;
162
163 protected SocketChannel channel;
164
165 protected SocketState socketState = new DISCONNECTED();
166
167 protected DispatchQueue dispatchQueue;
168 private DispatchSource readSource;
169 private DispatchSource writeSource;
170 protected CustomDispatchSource<Integer, Integer> drainOutboundSource;
171 protected CustomDispatchSource<Integer, Integer> yieldSource;
172
173 protected boolean useLocalHost = true;
174
175 int maxReadRate;
176 int maxWriteRate;
177 int receiveBufferSize = 1024*64;
178 int sendBufferSize = 1024*64;
179 boolean keepAlive = true;
180
181
182 public static final int IPTOS_LOWCOST = 0x02;
183 public static final int IPTOS_RELIABILITY = 0x04;
184 public static final int IPTOS_THROUGHPUT = 0x08;
185 public static final int IPTOS_LOWDELAY = 0x10;
186
187 int trafficClass = IPTOS_THROUGHPUT;
188
189 protected RateLimitingChannel rateLimitingChannel;
190 SocketAddress localAddress;
191 SocketAddress remoteAddress;
192
193 class RateLimitingChannel implements ReadableByteChannel, WritableByteChannel {
194
195 int read_allowance = maxReadRate;
196 boolean read_suspended = false;
197 int read_resume_counter = 0;
198 int write_allowance = maxWriteRate;
199 boolean write_suspended = false;
200
201 public void resetAllowance() {
202 if( read_allowance != maxReadRate || write_allowance != maxWriteRate) {
203 read_allowance = maxReadRate;
204 write_allowance = maxWriteRate;
205 if( write_suspended ) {
206 write_suspended = false;
207 resumeWrite();
208 }
209 if( read_suspended ) {
210 read_suspended = false;
211 resumeRead();
212 for( int i=0; i < read_resume_counter ; i++ ) {
213 resumeRead();
214 }
215 }
216 }
217 }
218
219 public int read(ByteBuffer dst) throws IOException {
220 if( maxReadRate ==0 ) {
221 return channel.read(dst);
222 } else {
223 int remaining = dst.remaining();
224 if( read_allowance ==0 || remaining ==0 ) {
225 return 0;
226 }
227
228 int reduction = 0;
229 if( remaining > read_allowance) {
230 reduction = remaining - read_allowance;
231 dst.limit(dst.limit() - reduction);
232 }
233 int rc=0;
234 try {
235 rc = channel.read(dst);
236 read_allowance -= rc;
237 } finally {
238 if( reduction!=0 ) {
239 if( dst.remaining() == 0 ) {
240 // we need to suspend the read now until we get
241 // a new allowance..
242 readSource.suspend();
243 read_suspended = true;
244 }
245 dst.limit(dst.limit() + reduction);
246 }
247 }
248 return rc;
249 }
250 }
251
252 public int write(ByteBuffer src) throws IOException {
253 if( maxWriteRate ==0 ) {
254 return channel.write(src);
255 } else {
256 int remaining = src.remaining();
257 if( write_allowance ==0 || remaining ==0 ) {
258 return 0;
259 }
260
261 int reduction = 0;
262 if( remaining > write_allowance) {
263 reduction = remaining - write_allowance;
264 src.limit(src.limit() - reduction);
265 }
266 int rc = 0;
267 try {
268 rc = channel.write(src);
269 write_allowance -= rc;
270 } finally {
271 if( reduction!=0 ) {
272 if( src.remaining() == 0 ) {
273 // we need to suspend the read now until we get
274 // a new allowance..
275 write_suspended = true;
276 suspendWrite();
277 }
278 src.limit(src.limit() + reduction);
279 }
280 }
281 return rc;
282 }
283 }
284
285 public boolean isOpen() {
286 return channel.isOpen();
287 }
288
289 public void close() throws IOException {
290 channel.close();
291 }
292
293 public void resumeRead() {
294 if( read_suspended ) {
295 read_resume_counter += 1;
296 } else {
297 _resumeRead();
298 }
299 }
300
301 }
302
303 private final Task CANCEL_HANDLER = new Task() {
304 public void run() {
305 socketState.onCanceled();
306 }
307 };
308
309 static final class OneWay {
310 final Object command;
311 final Retained retained;
312
313 public OneWay(Object command, Retained retained) {
314 this.command = command;
315 this.retained = retained;
316 }
317 }
318
319 public void connected(SocketChannel channel) throws IOException, Exception {
320 this.channel = channel;
321 initializeChannel();
322 this.socketState = new CONNECTED();
323 }
324
325 protected void initializeChannel() throws Exception {
326 this.channel.configureBlocking(false);
327 Socket socket = channel.socket();
328 try {
329 socket.setReuseAddress(true);
330 } catch (SocketException e) {
331 }
332 try {
333 socket.setSoLinger(true, 0);
334 } catch (SocketException e) {
335 }
336 try {
337 socket.setTrafficClass(trafficClass);
338 } catch (SocketException e) {
339 }
340 try {
341 socket.setKeepAlive(keepAlive);
342 } catch (SocketException e) {
343 }
344 try {
345 socket.setTcpNoDelay(true);
346 } catch (SocketException e) {
347 }
348 try {
349 socket.setReceiveBufferSize(receiveBufferSize);
350 } catch (SocketException e) {
351 }
352 try {
353 socket.setSendBufferSize(sendBufferSize);
354 } catch (SocketException e) {
355 }
356
357 if( channel!=null && codec!=null ) {
358 initializeCodec();
359 }
360 }
361
362 protected void initializeCodec() throws Exception {
363 codec.setReadableByteChannel(readChannel());
364 codec.setWritableByteChannel(writeChannel());
365 }
366
367 public void connecting(URI remoteLocation, URI localLocation) throws IOException, Exception {
368 this.channel = SocketChannel.open();
369 initializeChannel();
370 this.remoteLocation = remoteLocation;
371 this.localLocation = localLocation;
372
373 if (localLocation != null) {
374 InetSocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(localLocation.getHost()), localLocation.getPort());
375 channel.socket().bind(localAddress);
376 }
377
378 String host = resolveHostName(remoteLocation.getHost());
379 InetSocketAddress remoteAddress = new InetSocketAddress(host, remoteLocation.getPort());
380 channel.connect(remoteAddress);
381 this.socketState = new CONNECTING();
382 }
383
384
385 public DispatchQueue getDispatchQueue() {
386 return dispatchQueue;
387 }
388
389 public void setDispatchQueue(DispatchQueue queue) {
390 this.dispatchQueue = queue;
391 if(readSource!=null) readSource.setTargetQueue(queue);
392 if(writeSource!=null) writeSource.setTargetQueue(queue);
393 if(drainOutboundSource!=null) drainOutboundSource.setTargetQueue(queue);
394 if(yieldSource!=null) yieldSource.setTargetQueue(queue);
395 }
396
397 public void _start(Task onCompleted) {
398 try {
399 if (socketState.is(CONNECTING.class) ) {
400 trace("connecting...");
401 // this allows the connect to complete..
402 readSource = Dispatch.createSource(channel, SelectionKey.OP_CONNECT, dispatchQueue);
403 readSource.setEventHandler(new Task() {
404 public void run() {
405 if (getServiceState() != STARTED) {
406 return;
407 }
408 try {
409 trace("connected.");
410 channel.finishConnect();
411 readSource.setCancelHandler(null);
412 readSource.cancel();
413 readSource=null;
414 socketState = new CONNECTED();
415 onConnected();
416 } catch (IOException e) {
417 onTransportFailure(e);
418 }
419 }
420 });
421 readSource.setCancelHandler(CANCEL_HANDLER);
422 readSource.resume();
423
424 } else if (socketState.is(CONNECTED.class) ) {
425 dispatchQueue.execute(new Task() {
426 public void run() {
427 try {
428 trace("was connected.");
429 onConnected();
430 } catch (IOException e) {
431 onTransportFailure(e);
432 }
433 }
434 });
435 } else {
436 System.err.println("cannot be started. socket state is: "+socketState);
437 }
438 } finally {
439 if( onCompleted!=null ) {
440 onCompleted.run();
441 }
442 }
443 }
444
445 public void _stop(final Task onCompleted) {
446 trace("stopping.. at state: "+socketState);
447 socketState.onStop(onCompleted);
448 }
449
450 protected String resolveHostName(String host) throws UnknownHostException {
451 String localName = InetAddress.getLocalHost().getHostName();
452 if (localName != null && isUseLocalHost()) {
453 if (localName.equals(host)) {
454 return "localhost";
455 }
456 }
457 return host;
458 }
459
460 protected void onConnected() throws IOException {
461 yieldSource = Dispatch.createSource(EventAggregators.INTEGER_ADD, dispatchQueue);
462 yieldSource.setEventHandler(new Task() {
463 public void run() {
464 drainInbound();
465 }
466 });
467 yieldSource.resume();
468 drainOutboundSource = Dispatch.createSource(EventAggregators.INTEGER_ADD, dispatchQueue);
469 drainOutboundSource.setEventHandler(new Task() {
470 public void run() {
471 flush();
472 }
473 });
474 drainOutboundSource.resume();
475
476 readSource = Dispatch.createSource(channel, SelectionKey.OP_READ, dispatchQueue);
477 writeSource = Dispatch.createSource(channel, SelectionKey.OP_WRITE, dispatchQueue);
478
479 readSource.setCancelHandler(CANCEL_HANDLER);
480 writeSource.setCancelHandler(CANCEL_HANDLER);
481
482 readSource.setEventHandler(new Task() {
483 public void run() {
484 drainInbound();
485 }
486 });
487 writeSource.setEventHandler(new Task() {
488 public void run() {
489 flush();
490 }
491 });
492
493 if( maxReadRate !=0 || maxWriteRate !=0 ) {
494 rateLimitingChannel = new RateLimitingChannel();
495 schedualRateAllowanceReset();
496 }
497 listener.onTransportConnected();
498 }
499
500 private void schedualRateAllowanceReset() {
501 dispatchQueue.executeAfter(1, TimeUnit.SECONDS, new Task(){
502 public void run() {
503 if( !socketState.is(CONNECTED.class) ) {
504 return;
505 }
506 rateLimitingChannel.resetAllowance();
507 schedualRateAllowanceReset();
508 }
509 });
510 }
511
512 private void dispose() {
513 if( readSource!=null ) {
514 readSource.cancel();
515 readSource=null;
516 }
517
518 if( writeSource!=null ) {
519 writeSource.cancel();
520 writeSource=null;
521 }
522 this.codec = null;
523 }
524
525 public void onTransportFailure(IOException error) {
526 listener.onTransportFailure(error);
527 socketState.onCanceled();
528 }
529
530
531 public boolean full() {
532 return codec==null || codec.full();
533 }
534
535 boolean rejectingOffers;
536
537 public boolean offer(Object command) {
538 dispatchQueue.assertExecuting();
539 try {
540 if (!socketState.is(CONNECTED.class)) {
541 throw new IOException("Not connected.");
542 }
543 if (getServiceState() != STARTED) {
544 throw new IOException("Not running.");
545 }
546
547 ProtocolCodec.BufferState rc = codec.write(command);
548 rejectingOffers = codec.full();
549 switch (rc ) {
550 case FULL:
551 return false;
552 default:
553 drainOutboundSource.merge(1);
554 return true;
555 }
556 } catch (IOException e) {
557 onTransportFailure(e);
558 return false;
559 }
560
561 }
562
563 boolean writeResumedForCodecFlush = false;
564
565 /**
566 *
567 */
568 public void flush() {
569 dispatchQueue.assertExecuting();
570 if (getServiceState() != STARTED || !socketState.is(CONNECTED.class)) {
571 return;
572 }
573 try {
574 if( codec.flush() == ProtocolCodec.BufferState.EMPTY && transportFlush() ) {
575 if( writeResumedForCodecFlush) {
576 writeResumedForCodecFlush = false;
577 suspendWrite();
578 }
579 rejectingOffers = false;
580 listener.onRefill();
581
582 } else {
583 if(!writeResumedForCodecFlush) {
584 writeResumedForCodecFlush = true;
585 resumeWrite();
586 }
587 }
588 } catch (IOException e) {
589 onTransportFailure(e);
590 }
591 }
592
593 protected boolean transportFlush() throws IOException {
594 return true;
595 }
596
597 protected void drainInbound() {
598 if (!getServiceState().isStarted() || readSource.isSuspended()) {
599 return;
600 }
601 try {
602 long initial = codec.getReadCounter();
603 // Only process upto 2 x the read buffer worth of data at a time so we can give
604 // other connections a chance to process their requests.
605 while( codec.getReadCounter()-initial < codec.getReadBufferSize()<<2 ) {
606 Object command = codec.read();
607 if ( command!=null ) {
608 try {
609 listener.onTransportCommand(command);
610 } catch (Throwable e) {
611 e.printStackTrace();
612 onTransportFailure(new IOException("Transport listener failure."));
613 }
614
615 // the transport may be suspended after processing a command.
616 if (getServiceState() == STOPPED || readSource.isSuspended()) {
617 return;
618 }
619 } else {
620 return;
621 }
622 }
623 yieldSource.merge(1);
624 } catch (IOException e) {
625 onTransportFailure(e);
626 }
627 }
628
629 public SocketAddress getLocalAddress() {
630 return localAddress;
631 }
632
633 public SocketAddress getRemoteAddress() {
634 return remoteAddress;
635 }
636
637 private boolean assertConnected() {
638 try {
639 if ( !isConnected() ) {
640 throw new IOException("Not connected.");
641 }
642 return true;
643 } catch (IOException e) {
644 onTransportFailure(e);
645 }
646 return false;
647 }
648
649 public void suspendRead() {
650 if( isConnected() && readSource!=null ) {
651 readSource.suspend();
652 }
653 }
654
655
656 public void resumeRead() {
657 if( isConnected() && readSource!=null ) {
658 if( rateLimitingChannel!=null ) {
659 rateLimitingChannel.resumeRead();
660 } else {
661 _resumeRead();
662 }
663 }
664 }
665
666 private void _resumeRead() {
667 readSource.resume();
668 dispatchQueue.execute(new Task(){
669 public void run() {
670 drainInbound();
671 }
672 });
673 }
674
675 protected void suspendWrite() {
676 if( isConnected() && writeSource!=null ) {
677 writeSource.suspend();
678 }
679 }
680
681 protected void resumeWrite() {
682 if( isConnected() && writeSource!=null ) {
683 writeSource.resume();
684 }
685 }
686
687 public TransportListener getTransportListener() {
688 return listener;
689 }
690
691 public void setTransportListener(TransportListener transportListener) {
692 this.listener = transportListener;
693 }
694
695 public ProtocolCodec getProtocolCodec() {
696 return codec;
697 }
698
699 public void setProtocolCodec(ProtocolCodec protocolCodec) throws Exception {
700 this.codec = protocolCodec;
701 if( channel!=null && codec!=null ) {
702 initializeCodec();
703 }
704 }
705
706 public boolean isConnected() {
707 return socketState.is(CONNECTED.class);
708 }
709
710 public boolean isClosed() {
711 return getServiceState() == STOPPED;
712 }
713
714 public boolean isUseLocalHost() {
715 return useLocalHost;
716 }
717
718 /**
719 * Sets whether 'localhost' or the actual local host name should be used to
720 * make local connections. On some operating systems such as Macs its not
721 * possible to connect as the local host name so localhost is better.
722 */
723 public void setUseLocalHost(boolean useLocalHost) {
724 this.useLocalHost = useLocalHost;
725 }
726
727 private void trace(String message) {
728 // TODO:
729 }
730
731 public SocketChannel getSocketChannel() {
732 return channel;
733 }
734
735 public ReadableByteChannel readChannel() {
736 if(rateLimitingChannel!=null) {
737 return rateLimitingChannel;
738 } else {
739 return channel;
740 }
741 }
742
743 public WritableByteChannel writeChannel() {
744 if(rateLimitingChannel!=null) {
745 return rateLimitingChannel;
746 } else {
747 return channel;
748 }
749 }
750
751 public int getMaxReadRate() {
752 return maxReadRate;
753 }
754
755 public void setMaxReadRate(int maxReadRate) {
756 this.maxReadRate = maxReadRate;
757 }
758
759 public int getMaxWriteRate() {
760 return maxWriteRate;
761 }
762
763 public void setMaxWriteRate(int maxWriteRate) {
764 this.maxWriteRate = maxWriteRate;
765 }
766
767 public int getTrafficClass() {
768 return trafficClass;
769 }
770
771 public void setTrafficClass(int trafficClass) {
772 this.trafficClass = trafficClass;
773 }
774
775 public int getReceiveBufferSize() {
776 return receiveBufferSize;
777 }
778
779 public void setReceiveBufferSize(int receiveBufferSize) {
780 this.receiveBufferSize = receiveBufferSize;
781 }
782
783 public int getSendBufferSize() {
784 return sendBufferSize;
785 }
786
787 public void setSendBufferSize(int sendBufferSize) {
788 this.sendBufferSize = sendBufferSize;
789 }
790
791 public boolean isKeepAlive() {
792 return keepAlive;
793 }
794
795 public void setKeepAlive(boolean keepAlive) {
796 this.keepAlive = keepAlive;
797 }
798
799 }