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