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}