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    }