001/**
002gxfdgvdfg * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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 */
017package org.apache.activemq.transport.tcp;
018
019import java.io.DataInputStream;
020import java.io.DataOutputStream;
021import java.io.IOException;
022import java.io.InterruptedIOException;
023import java.net.InetAddress;
024import java.net.InetSocketAddress;
025import java.net.Socket;
026import java.net.SocketAddress;
027import java.net.SocketException;
028import java.net.SocketTimeoutException;
029import java.net.URI;
030import java.net.UnknownHostException;
031import java.nio.ByteBuffer;
032import java.util.HashMap;
033import java.util.Map;
034import java.util.concurrent.CountDownLatch;
035import java.util.concurrent.TimeUnit;
036import java.util.concurrent.atomic.AtomicReference;
037
038import javax.net.SocketFactory;
039
040import org.apache.activemq.Service;
041import org.apache.activemq.TransportLoggerSupport;
042import org.apache.activemq.thread.TaskRunnerFactory;
043import org.apache.activemq.transport.Transport;
044import org.apache.activemq.transport.TransportThreadSupport;
045import org.apache.activemq.util.InetAddressUtil;
046import org.apache.activemq.util.IntrospectionSupport;
047import org.apache.activemq.util.ServiceStopper;
048import org.apache.activemq.wireformat.WireFormat;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052/**
053 * An implementation of the {@link Transport} interface using raw tcp/ip
054 *
055 * @author David Martin Clavo david(dot)martin(dot)clavo(at)gmail.com (logging improvement modifications)
056 *
057 */
058public class TcpTransport extends TransportThreadSupport implements Transport, Service, Runnable {
059    private static final Logger LOG = LoggerFactory.getLogger(TcpTransport.class);
060    protected final URI remoteLocation;
061    protected final URI localLocation;
062    protected final WireFormat wireFormat;
063
064    protected int connectionTimeout = 30000;
065    protected int soTimeout;
066    protected int socketBufferSize = 64 * 1024;
067    protected int ioBufferSize = 8 * 1024;
068    protected boolean closeAsync=true;
069    protected Socket socket;
070    protected DataOutputStream dataOut;
071    protected DataInputStream dataIn;
072    protected TimeStampStream buffOut = null;
073
074    protected final InitBuffer initBuffer;
075
076    /**
077     * The Traffic Class to be set on the socket.
078     */
079    protected int trafficClass = 0;
080    /**
081     * Keeps track of attempts to set the Traffic Class on the socket.
082     */
083    private boolean trafficClassSet = false;
084    /**
085     * Prevents setting both the Differentiated Services and Type of Service
086     * transport options at the same time, since they share the same spot in
087     * the TCP/IP packet headers.
088     */
089    protected boolean diffServChosen = false;
090    protected boolean typeOfServiceChosen = false;
091    /**
092     * trace=true -> the Transport stack where this TcpTransport
093     * object will be, will have a TransportLogger layer
094     * trace=false -> the Transport stack where this TcpTransport
095     * object will be, will NOT have a TransportLogger layer, and therefore
096     * will never be able to print logging messages.
097     * This parameter is most probably set in Connection or TransportConnector URIs.
098     */
099    protected boolean trace = false;
100    /**
101     * Name of the LogWriter implementation to use.
102     * Names are mapped to classes in the resources/META-INF/services/org/apache/activemq/transport/logwriters directory.
103     * This parameter is most probably set in Connection or TransportConnector URIs.
104     */
105    protected String logWriterName = TransportLoggerSupport.defaultLogWriterName;
106    /**
107     * Specifies if the TransportLogger will be manageable by JMX or not.
108     * Also, as long as there is at least 1 TransportLogger which is manageable,
109     * a TransportLoggerControl MBean will me created.
110     */
111    protected boolean dynamicManagement = false;
112    /**
113     * startLogging=true -> the TransportLogger object of the Transport stack
114     * will initially write messages to the log.
115     * startLogging=false -> the TransportLogger object of the Transport stack
116     * will initially NOT write messages to the log.
117     * This parameter only has an effect if trace == true.
118     * This parameter is most probably set in Connection or TransportConnector URIs.
119     */
120    protected boolean startLogging = true;
121    /**
122     * Specifies the port that will be used by the JMX server to manage
123     * the TransportLoggers.
124     * This should only be set in an URI by a client (producer or consumer) since
125     * a broker will already create a JMX server.
126     * It is useful for people who test a broker and clients in the same machine
127     * and want to control both via JMX; a different port will be needed.
128     */
129    protected int jmxPort = 1099;
130    protected boolean useLocalHost = false;
131    protected int minmumWireFormatVersion;
132    protected SocketFactory socketFactory;
133    protected final AtomicReference<CountDownLatch> stoppedLatch = new AtomicReference<CountDownLatch>();
134    protected volatile int receiveCounter;
135
136    private Map<String, Object> socketOptions;
137    private int soLinger = Integer.MIN_VALUE;
138    private Boolean keepAlive;
139    private Boolean tcpNoDelay;
140    private Thread runnerThread;
141
142    /**
143     * Connect to a remote Node - e.g. a Broker
144     *
145     * @param wireFormat
146     * @param socketFactory
147     * @param remoteLocation
148     * @param localLocation - e.g. local InetAddress and local port
149     * @throws IOException
150     * @throws UnknownHostException
151     */
152    public TcpTransport(WireFormat wireFormat, SocketFactory socketFactory, URI remoteLocation,
153                        URI localLocation) throws UnknownHostException, IOException {
154        this.wireFormat = wireFormat;
155        this.socketFactory = socketFactory;
156        try {
157            this.socket = socketFactory.createSocket();
158        } catch (SocketException e) {
159            this.socket = null;
160        }
161        this.remoteLocation = remoteLocation;
162        this.localLocation = localLocation;
163        this.initBuffer = null;
164        setDaemon(false);
165    }
166
167    /**
168     * Initialize from a server Socket
169     *
170     * @param wireFormat
171     * @param socket
172     * @throws IOException
173     */
174    public TcpTransport(WireFormat wireFormat, Socket socket) throws IOException {
175        this(wireFormat, socket, null);
176    }
177
178    public TcpTransport(WireFormat wireFormat, Socket socket, InitBuffer initBuffer) throws IOException {
179        this.wireFormat = wireFormat;
180        this.socket = socket;
181        this.remoteLocation = null;
182        this.localLocation = null;
183        this.initBuffer = initBuffer;
184        setDaemon(true);
185    }
186
187    /**
188     * A one way asynchronous send
189     */
190    @Override
191    public void oneway(Object command) throws IOException {
192        checkStarted();
193        wireFormat.marshal(command, dataOut);
194        dataOut.flush();
195    }
196
197    /**
198     * @return pretty print of 'this'
199     */
200    @Override
201    public String toString() {
202        return "" + (socket.isConnected() ? "tcp://" + socket.getInetAddress() + ":" + socket.getPort() + "@" + socket.getLocalPort()
203                : (localLocation != null ? localLocation : remoteLocation)) ;
204    }
205
206    /**
207     * reads packets from a Socket
208     */
209    @Override
210    public void run() {
211        LOG.trace("TCP consumer thread for " + this + " starting");
212        this.runnerThread=Thread.currentThread();
213        try {
214            while (!isStopped()) {
215                doRun();
216            }
217        } catch (IOException e) {
218            stoppedLatch.get().countDown();
219            onException(e);
220        } catch (Throwable e){
221            stoppedLatch.get().countDown();
222            IOException ioe=new IOException("Unexpected error occurred: " + e);
223            ioe.initCause(e);
224            onException(ioe);
225        }finally {
226            stoppedLatch.get().countDown();
227        }
228    }
229
230    protected void doRun() throws IOException {
231        try {
232            Object command = readCommand();
233            doConsume(command);
234        } catch (SocketTimeoutException e) {
235        } catch (InterruptedIOException e) {
236        }
237    }
238
239    protected Object readCommand() throws IOException {
240        return wireFormat.unmarshal(dataIn);
241    }
242
243    // Properties
244    // -------------------------------------------------------------------------
245    public String getDiffServ() {
246        // This is the value requested by the user by setting the Tcp Transport
247        // options. If the socket hasn't been created, then this value may not
248        // reflect the value returned by Socket.getTrafficClass().
249        return Integer.toString(this.trafficClass);
250    }
251
252    public void setDiffServ(String diffServ) throws IllegalArgumentException {
253        this.trafficClass = QualityOfServiceUtils.getDSCP(diffServ);
254        this.diffServChosen = true;
255    }
256
257    public int getTypeOfService() {
258        // This is the value requested by the user by setting the Tcp Transport
259        // options. If the socket hasn't been created, then this value may not
260        // reflect the value returned by Socket.getTrafficClass().
261        return this.trafficClass;
262    }
263
264    public void setTypeOfService(int typeOfService) {
265        this.trafficClass = QualityOfServiceUtils.getToS(typeOfService);
266        this.typeOfServiceChosen = true;
267    }
268
269    public boolean isTrace() {
270        return trace;
271    }
272
273    public void setTrace(boolean trace) {
274        this.trace = trace;
275    }
276
277    public String getLogWriterName() {
278        return logWriterName;
279    }
280
281    public void setLogWriterName(String logFormat) {
282        this.logWriterName = logFormat;
283    }
284
285    public boolean isDynamicManagement() {
286        return dynamicManagement;
287    }
288
289    public void setDynamicManagement(boolean useJmx) {
290        this.dynamicManagement = useJmx;
291    }
292
293    public boolean isStartLogging() {
294        return startLogging;
295    }
296
297    public void setStartLogging(boolean startLogging) {
298        this.startLogging = startLogging;
299    }
300
301    public int getJmxPort() {
302        return jmxPort;
303    }
304
305    public void setJmxPort(int jmxPort) {
306        this.jmxPort = jmxPort;
307    }
308
309    public int getMinmumWireFormatVersion() {
310        return minmumWireFormatVersion;
311    }
312
313    public void setMinmumWireFormatVersion(int minmumWireFormatVersion) {
314        this.minmumWireFormatVersion = minmumWireFormatVersion;
315    }
316
317    public boolean isUseLocalHost() {
318        return useLocalHost;
319    }
320
321    /**
322     * Sets whether 'localhost' or the actual local host name should be used to
323     * make local connections. On some operating systems such as Macs its not
324     * possible to connect as the local host name so localhost is better.
325     */
326    public void setUseLocalHost(boolean useLocalHost) {
327        this.useLocalHost = useLocalHost;
328    }
329
330    public int getSocketBufferSize() {
331        return socketBufferSize;
332    }
333
334    /**
335     * Sets the buffer size to use on the socket
336     */
337    public void setSocketBufferSize(int socketBufferSize) {
338        this.socketBufferSize = socketBufferSize;
339    }
340
341    public int getSoTimeout() {
342        return soTimeout;
343    }
344
345    /**
346     * Sets the socket timeout
347     */
348    public void setSoTimeout(int soTimeout) {
349        this.soTimeout = soTimeout;
350    }
351
352    public int getConnectionTimeout() {
353        return connectionTimeout;
354    }
355
356    /**
357     * Sets the timeout used to connect to the socket
358     */
359    public void setConnectionTimeout(int connectionTimeout) {
360        this.connectionTimeout = connectionTimeout;
361    }
362
363    public Boolean getKeepAlive() {
364        return keepAlive;
365    }
366
367    /**
368     * Enable/disable TCP KEEP_ALIVE mode
369     */
370    public void setKeepAlive(Boolean keepAlive) {
371        this.keepAlive = keepAlive;
372    }
373
374    /**
375     * Enable/disable soLinger
376     * @param soLinger enabled if > -1, disabled if == -1, system default otherwise
377     */
378    public void setSoLinger(int soLinger) {
379        this.soLinger = soLinger;
380    }
381
382    public int getSoLinger() {
383        return soLinger;
384    }
385
386    public Boolean getTcpNoDelay() {
387        return tcpNoDelay;
388    }
389
390    /**
391     * Enable/disable the TCP_NODELAY option on the socket
392     */
393    public void setTcpNoDelay(Boolean tcpNoDelay) {
394        this.tcpNoDelay = tcpNoDelay;
395    }
396
397    /**
398     * @return the ioBufferSize
399     */
400    public int getIoBufferSize() {
401        return this.ioBufferSize;
402    }
403
404    /**
405     * @param ioBufferSize the ioBufferSize to set
406     */
407    public void setIoBufferSize(int ioBufferSize) {
408        this.ioBufferSize = ioBufferSize;
409    }
410
411    /**
412     * @return the closeAsync
413     */
414    public boolean isCloseAsync() {
415        return closeAsync;
416    }
417
418    /**
419     * @param closeAsync the closeAsync to set
420     */
421    public void setCloseAsync(boolean closeAsync) {
422        this.closeAsync = closeAsync;
423    }
424
425    // Implementation methods
426    // -------------------------------------------------------------------------
427    protected String resolveHostName(String host) throws UnknownHostException {
428        if (isUseLocalHost()) {
429            String localName = InetAddressUtil.getLocalHostName();
430            if (localName != null && localName.equals(host)) {
431                return "localhost";
432            }
433        }
434        return host;
435    }
436
437    /**
438     * Configures the socket for use
439     *
440     * @param sock  the socket
441     * @throws SocketException, IllegalArgumentException if setting the options
442     *         on the socket failed.
443     */
444    protected void initialiseSocket(Socket sock) throws SocketException, IllegalArgumentException {
445        if (socketOptions != null) {
446            // copy the map as its used values is being removed when calling setProperties
447            // and we need to be able to set the options again in case socket is re-initailized
448            Map<String, Object> copy = new HashMap<String, Object>(socketOptions);
449            IntrospectionSupport.setProperties(socket, copy);
450            if (!copy.isEmpty()) {
451                throw new IllegalArgumentException("Invalid socket parameters: " + copy);
452            }
453        }
454
455        try {
456            sock.setReceiveBufferSize(socketBufferSize);
457            sock.setSendBufferSize(socketBufferSize);
458        } catch (SocketException se) {
459            LOG.warn("Cannot set socket buffer size = " + socketBufferSize);
460            LOG.debug("Cannot set socket buffer size. Reason: " + se.getMessage() + ". This exception is ignored.", se);
461        }
462        sock.setSoTimeout(soTimeout);
463
464        if (keepAlive != null) {
465            sock.setKeepAlive(keepAlive.booleanValue());
466        }
467
468        if (soLinger > -1) {
469            sock.setSoLinger(true, soLinger);
470        } else if (soLinger == -1) {
471            sock.setSoLinger(false, 0);
472        }
473        if (tcpNoDelay != null) {
474            sock.setTcpNoDelay(tcpNoDelay.booleanValue());
475        }
476        if (!this.trafficClassSet) {
477            this.trafficClassSet = setTrafficClass(sock);
478        }
479    }
480
481    @Override
482    protected void doStart() throws Exception {
483        connect();
484        stoppedLatch.set(new CountDownLatch(1));
485        super.doStart();
486    }
487
488    protected void connect() throws Exception {
489
490        if (socket == null && socketFactory == null) {
491            throw new IllegalStateException("Cannot connect if the socket or socketFactory have not been set");
492        }
493
494        InetSocketAddress localAddress = null;
495        InetSocketAddress remoteAddress = null;
496
497        if (localLocation != null) {
498            localAddress = new InetSocketAddress(InetAddress.getByName(localLocation.getHost()),
499                                                 localLocation.getPort());
500        }
501
502        if (remoteLocation != null) {
503            String host = resolveHostName(remoteLocation.getHost());
504            remoteAddress = new InetSocketAddress(host, remoteLocation.getPort());
505        }
506        // Set the traffic class before the socket is connected when possible so
507        // that the connection packets are given the correct traffic class.
508        this.trafficClassSet = setTrafficClass(socket);
509
510        if (socket != null) {
511
512            if (localAddress != null) {
513                socket.bind(localAddress);
514            }
515
516            // If it's a server accepted socket.. we don't need to connect it
517            // to a remote address.
518            if (remoteAddress != null) {
519                if (connectionTimeout >= 0) {
520                    socket.connect(remoteAddress, connectionTimeout);
521                } else {
522                    socket.connect(remoteAddress);
523                }
524            }
525
526        } else {
527            // For SSL sockets.. you can't create an unconnected socket :(
528            // This means the timout option are not supported either.
529            if (localAddress != null) {
530                socket = socketFactory.createSocket(remoteAddress.getAddress(), remoteAddress.getPort(),
531                                                    localAddress.getAddress(), localAddress.getPort());
532            } else {
533                socket = socketFactory.createSocket(remoteAddress.getAddress(), remoteAddress.getPort());
534            }
535        }
536
537        initialiseSocket(socket);
538        initializeStreams();
539    }
540
541    @Override
542    protected void doStop(ServiceStopper stopper) throws Exception {
543        if (LOG.isDebugEnabled()) {
544            LOG.debug("Stopping transport " + this);
545        }
546
547        // Closing the streams flush the sockets before closing.. if the socket
548        // is hung.. then this hangs the close.
549        // closeStreams();
550        if (socket != null) {
551            if (closeAsync) {
552                //closing the socket can hang also
553                final CountDownLatch latch = new CountDownLatch(1);
554
555                // need a async task for this
556                final TaskRunnerFactory taskRunnerFactory = new TaskRunnerFactory();
557                taskRunnerFactory.execute(new Runnable() {
558                    @Override
559                    public void run() {
560                        LOG.trace("Closing socket {}", socket);
561                        try {
562                            socket.close();
563                            LOG.debug("Closed socket {}", socket);
564                        } catch (IOException e) {
565                            if (LOG.isDebugEnabled()) {
566                                LOG.debug("Caught exception closing socket " + socket + ". This exception will be ignored.", e);
567                            }
568                        } finally {
569                            latch.countDown();
570                        }
571                    }
572                });
573
574                try {
575                    latch.await(1,TimeUnit.SECONDS);
576                } catch (InterruptedException e) {
577                    Thread.currentThread().interrupt();
578                } finally {
579                    taskRunnerFactory.shutdownNow();
580                }
581
582            } else {
583                // close synchronously
584                LOG.trace("Closing socket {}", socket);
585                try {
586                    socket.close();
587                    LOG.debug("Closed socket {}", socket);
588                } catch (IOException e) {
589                    if (LOG.isDebugEnabled()) {
590                        LOG.debug("Caught exception closing socket " + socket + ". This exception will be ignored.", e);
591                    }
592                }
593            }
594        }
595    }
596
597    /**
598     * Override so that stop() blocks until the run thread is no longer running.
599     */
600    @Override
601    public void stop() throws Exception {
602        super.stop();
603        CountDownLatch countDownLatch = stoppedLatch.get();
604        if (countDownLatch != null && Thread.currentThread() != this.runnerThread) {
605            countDownLatch.await(1,TimeUnit.SECONDS);
606        }
607    }
608
609    protected void initializeStreams() throws Exception {
610        TcpBufferedInputStream buffIn = new TcpBufferedInputStream(socket.getInputStream(), ioBufferSize) {
611            @Override
612            public int read() throws IOException {
613                receiveCounter++;
614                return super.read();
615            }
616            @Override
617            public int read(byte[] b, int off, int len) throws IOException {
618                receiveCounter++;
619                return super.read(b, off, len);
620            }
621            @Override
622            public long skip(long n) throws IOException {
623                receiveCounter++;
624                return super.skip(n);
625            }
626            @Override
627            protected void fill() throws IOException {
628                receiveCounter++;
629                super.fill();
630            }
631        };
632        //Unread the initBuffer that was used for protocol detection if it exists
633        //so the stream can start over
634        if (initBuffer != null) {
635            buffIn.unread(initBuffer.buffer.array());
636        }
637        this.dataIn = new DataInputStream(buffIn);
638        TcpBufferedOutputStream outputStream = new TcpBufferedOutputStream(socket.getOutputStream(), ioBufferSize);
639        this.dataOut = new DataOutputStream(outputStream);
640        this.buffOut = outputStream;
641
642    }
643
644    protected void closeStreams() throws IOException {
645        if (dataOut != null) {
646            dataOut.close();
647        }
648        if (dataIn != null) {
649            dataIn.close();
650        }
651    }
652
653    public void setSocketOptions(Map<String, Object> socketOptions) {
654        this.socketOptions = new HashMap<String, Object>(socketOptions);
655    }
656
657    @Override
658    public String getRemoteAddress() {
659        if (socket != null) {
660            SocketAddress address = socket.getRemoteSocketAddress();
661            if (address instanceof InetSocketAddress) {
662                return "tcp://" + ((InetSocketAddress)address).getAddress().getHostAddress() + ":" + ((InetSocketAddress)address).getPort();
663            } else {
664                return "" + socket.getRemoteSocketAddress();
665            }
666        }
667        return null;
668    }
669
670    @Override
671    public <T> T narrow(Class<T> target) {
672        if (target == Socket.class) {
673            return target.cast(socket);
674        } else if ( target == TimeStampStream.class) {
675            return target.cast(buffOut);
676        }
677        return super.narrow(target);
678    }
679
680    @Override
681    public int getReceiveCounter() {
682        return receiveCounter;
683    }
684
685    public static class InitBuffer {
686        public final int readSize;
687        public final ByteBuffer buffer;
688
689        public InitBuffer(int readSize, ByteBuffer buffer) {
690            if (buffer == null) {
691                throw new IllegalArgumentException("Null buffer not allowed.");
692            }
693            this.readSize = readSize;
694            this.buffer = buffer;
695        }
696    }
697
698    /**
699     * @param sock The socket on which to set the Traffic Class.
700     * @return Whether or not the Traffic Class was set on the given socket.
701     * @throws SocketException if the system does not support setting the
702     *         Traffic Class.
703     * @throws IllegalArgumentException if both the Differentiated Services and
704     *         Type of Services transport options have been set on the same
705     *         connection.
706     */
707    private boolean setTrafficClass(Socket sock) throws SocketException,
708            IllegalArgumentException {
709        if (sock == null
710            || (!this.diffServChosen && !this.typeOfServiceChosen)) {
711            return false;
712        }
713        if (this.diffServChosen && this.typeOfServiceChosen) {
714            throw new IllegalArgumentException("Cannot set both the "
715                + " Differentiated Services and Type of Services transport "
716                + " options on the same connection.");
717        }
718
719        sock.setTrafficClass(this.trafficClass);
720
721        int resultTrafficClass = sock.getTrafficClass();
722        if (this.trafficClass != resultTrafficClass) {
723            // In the case where the user has specified the ECN bits (e.g. in
724            // Type of Service) but the system won't allow the ECN bits to be
725            // set or in the case where setting the traffic class failed for
726            // other reasons, emit a warning.
727            if ((this.trafficClass >> 2) == (resultTrafficClass >> 2)
728                    && (this.trafficClass & 3) != (resultTrafficClass & 3)) {
729                LOG.warn("Attempted to set the Traffic Class to "
730                    + this.trafficClass + " but the result Traffic Class was "
731                    + resultTrafficClass + ". Please check that your system "
732                    + "allows you to set the ECN bits (the first two bits).");
733            } else {
734                LOG.warn("Attempted to set the Traffic Class to "
735                    + this.trafficClass + " but the result Traffic Class was "
736                    + resultTrafficClass + ". Please check that your system "
737                         + "supports java.net.setTrafficClass.");
738            }
739            return false;
740        }
741        // Reset the guards that prevent both the Differentiated Services
742        // option and the Type of Service option from being set on the same
743        // connection.
744        this.diffServChosen = false;
745        this.typeOfServiceChosen = false;
746        return true;
747    }
748
749    public WireFormat getWireFormat() {
750        return wireFormat;
751    }
752}