/*
* JBoss, Home of Professional Open Source
* Copyright 2010 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.client.hotrod.impl.transport.tcp;
import static org.infinispan.io.UnsignedNumeric.readUnsignedInt;
import static org.infinispan.io.UnsignedNumeric.readUnsignedLong;
import static org.infinispan.io.UnsignedNumeric.writeUnsignedInt;
import static org.infinispan.io.UnsignedNumeric.writeUnsignedLong;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicLong;
import net.jcip.annotations.ThreadSafe;
import org.infinispan.client.hotrod.exceptions.TransportException;
import org.infinispan.client.hotrod.impl.transport.AbstractTransport;
import org.infinispan.client.hotrod.impl.transport.TransportFactory;
import org.infinispan.client.hotrod.logging.Log;
import org.infinispan.client.hotrod.logging.LogFactory;
import org.infinispan.util.Util;
/**
* Transport implementation based on TCP.
*
* @author Mircea.Markus@jboss.com
* @since 4.1
*/
@ThreadSafe
public class TcpTransport extends AbstractTransport {
//needed for debugging
private static AtomicLong ID_COUNTER = new AtomicLong(0);
private static final Log log = LogFactory.getLog(TcpTransport.class, Log.class);
private static final boolean trace = log.isTraceEnabled();
private final Socket socket;
private final SocketChannel socketChannel;
private final InputStream socketInputStream;
private final BufferedOutputStream socketOutputStream;
private final InetSocketAddress serverAddress;
private final long id = ID_COUNTER.incrementAndGet();
private volatile boolean invalid;
public TcpTransport(InetSocketAddress serverAddress, TransportFactory transportFactory) {
super(transportFactory);
this.serverAddress = serverAddress;
try {
socketChannel = SocketChannel.open();
socket = socketChannel.socket();
socket.connect(serverAddress, transportFactory.getConnectTimeout());
socket.setTcpNoDelay(transportFactory.isTcpNoDelay());
socket.setSoTimeout(transportFactory.getSoTimeout());
socketInputStream = new BufferedInputStream(socket.getInputStream(), socket.getReceiveBufferSize());
// ensure we don't send a packet for every output byte
socketOutputStream = new BufferedOutputStream(socket.getOutputStream(), socket.getSendBufferSize());
} catch (IOException e) {
String message = String.format("Could not connect to server: %s", serverAddress);
log.couldNotConnectToServer(serverAddress, e);
throw new TransportException(message, e);
}
}
@Override
public void writeVInt(int vInt) {
try {
writeUnsignedInt(socketOutputStream, vInt);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
}
@Override
public void writeVLong(long l) {
try {
writeUnsignedLong(socketOutputStream, l);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
}
@Override
public long readVLong() {
try {
return readUnsignedLong(socketInputStream);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
}
@Override
public int readVInt() {
try {
return readUnsignedInt(socketInputStream);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
}
@Override
protected void writeBytes(byte[] toAppend) {
try {
socketOutputStream.write(toAppend);
if (trace) {
log.tracef("Wrote %d bytes", toAppend.length);
}
} catch (IOException e) {
invalid = true;
throw new TransportException("Problems writing data to stream", e);
}
}
@Override
public void writeByte(short toWrite) {
try {
socketOutputStream.write(toWrite);
if (trace) {
log.tracef("Wrote byte %d", toWrite);
}
} catch (IOException e) {
invalid = true;
throw new TransportException("Problems writing data to stream", e);
}
}
@Override
public void flush() {
try {
socketOutputStream.flush();
if (trace) {
log.tracef("Flushed socket: %s", socket);
}
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
}
@Override
public short readByte() {
int resultInt;
try {
resultInt = socketInputStream.read();
if (trace)
log.tracef("Read byte %d from socket input in %s", resultInt, socket);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
if (resultInt == -1) {
throw new TransportException("End of stream reached!");
}
return (short) resultInt;
}
@Override
public void release() {
try {
socket.close();
} catch (IOException e) {
invalid = true;
log.errorClosingSocket(this, e);
}
}
@Override
public byte[] readByteArray(final int size) {
byte[] result = new byte[size];
boolean done = false;
int offset = 0;
do {
int read;
try {
int len = size - offset;
if (trace) {
log.tracef("Offset: %d, len=%d, size=%d", offset, len, size);
}
read = socketInputStream.read(result, offset, len);
} catch (IOException e) {
invalid = true;
throw new TransportException(e);
}
if (read == -1) {
throw new RuntimeException("End of stream reached!");
}
if (read + offset == size) {
done = true;
} else {
offset += read;
if (offset > result.length) {
throw new IllegalStateException("Assertion!");
}
}
} while (!done);
if (trace) {
log.tracef("Successfully read array with size: %d", size);
}
return result;
}
public InetSocketAddress getServerAddress() {
return serverAddress;
}
@Override
public String toString() {
return "TcpTransport{" +
"socket=" + socket +
", serverAddress=" + serverAddress +
", id =" + id +
"} ";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TcpTransport that = (TcpTransport) o;
if (serverAddress != null ? !serverAddress.equals(that.serverAddress) : that.serverAddress != null) {
return false;
}
if (socket != null ? !socket.equals(that.socket) : that.socket != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = socket != null ? socket.hashCode() : 0;
result = 31 * result + (serverAddress != null ? serverAddress.hashCode() : 0);
return result;
}
public void destroy() {
try {
if (socketInputStream != null) socketInputStream.close();
if (socketOutputStream != null) socketOutputStream.close();
if (socketChannel != null) socketChannel.close();
if (socket != null) socket.close();
if (trace) {
log.tracef("Successfully closed socket: %s", socket);
}
} catch (IOException e) {
invalid = true;
log.errorClosingSocket(this, e);
// Just in case an exception is thrown, make sure they're fully closed
Util.close(socketInputStream, socketOutputStream, socketChannel);
Util.close(socket);
}
}
public boolean isValid() {
return !socket.isClosed() && !invalid;
}
public long getId() {
return id;
}
@Override
public byte[] dumpStream() {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
socket.setSoTimeout(5000);
// Read 32kb at most
for (int i = 0; i < 32768; i++) {
int b = socketInputStream.read();
if (b < 0) {
break;
}
os.write(b);
}
} catch (IOException e) {
// Ignore
} finally {
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
return os.toByteArray();
}
}