/**
* Copyright 2013-2015 Seagate Technology LLC.
*
* This Source Code Form is subject to the terms of the Mozilla
* Public License, v. 2.0. If a copy of the MPL was not
* distributed with this file, You can obtain one at
* https://mozilla.org/MP:/2.0/.
*
* This program is distributed in the hope that it will be useful,
* but is provided AS-IS, WITHOUT ANY WARRANTY; including without
* the implied warranty of MERCHANTABILITY, NON-INFRINGEMENT or
* FITNESS FOR A PARTICULAR PURPOSE. See the Mozilla Public
* License for more details.
*
* See www.openkinetic.org for more project information
*/
package com.seagate.kinetic.client.io.provider.tcp;
import io.netty.handler.codec.CorruptedFrameException;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;
import kinetic.client.ClientConfiguration;
import kinetic.client.KineticException;
import com.google.protobuf.InvalidProtocolBufferException;
import com.seagate.kinetic.client.io.provider.spi.ClientMessageService;
import com.seagate.kinetic.client.io.provider.spi.ClientTransportProvider;
import com.seagate.kinetic.common.lib.KineticMessage;
import com.seagate.kinetic.proto.Kinetic.Command;
import com.seagate.kinetic.proto.Kinetic.Message;
import com.seagate.kinetic.proto.Kinetic.Message.Builder;
/**
*
* Tcp transport provider.This class provides TCP transport support for the
* Kinetic client runtime.
* <p>
*
* @see com.seagate.kinetic.client.io.provider.nio.tcp.TcpNioTransportProvider
*
* @author James Hughes
* @author chiaming yang
*/
public class TcpTransportProvider implements ClientTransportProvider, Runnable {
// my logger
private final Logger logger = Logger
.getLogger(TcpTransportProvider.class.getName());
// input socket read thread
private Thread myThread = null;
// client socket
private Socket socket = null;
// input stream
private InputStream is = null;
// for v2 protocol
DataInputStream dis = null;
// output stream
private OutputStream os = null;
// flag running flag
private volatile boolean isRunning = true;
private ClientMessageService mservice = null;
public TcpTransportProvider() {
}
/**
* {@inheritDoc}
*/
@Override
public void init(ClientMessageService mservice)
throws KineticException {
ClientConfiguration config = mservice.getConfiguration();
this.mservice = mservice;
/**
* Create socket to server and get I/O streams.
*
* @throws KineticException
* if any I/O or internal exception occurred.
*/
try {
SocketAddress address = new InetSocketAddress(config.getHost(),
config.getPort());
socket = new Socket();
socket.connect(address, config.getConnectTimeoutMillis());
is = socket.getInputStream();
dis = new DataInputStream(is);
os = socket.getOutputStream();
this.myThread = new Thread(this);
this.myThread.setName("IoHandler-" + config.getHost() + "-"
+ config.getPort());
this.myThread.start();
logger.info("tcp-non-nio transport initialized ...");
} catch (IOException e) {
this.close();
logger.log(Level.SEVERE, e.getMessage(), e);
throw new KineticException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void write(KineticMessage km) throws IOException {
Message.Builder message = (Builder) km.getMessage();
if (logger.isLoggable(Level.FINEST)) {
logger.finest("writing message: " + message);
}
try {
// get value to write separately
byte[] value = km.getValue();
// write 9 byte header
ByteArrayOutputStream baos = new ByteArrayOutputStream(9);
DataOutputStream dos = new DataOutputStream(baos);
// magic
dos.writeByte((byte) 'F');
// set value to an empty value
// message.setValue(ByteString.EMPTY);
// build message (without value) to write
Message msg = message.build();
// get proto message bytes
byte[] protoMessageBytes = msg.toByteArray();
// write message len
dos.writeInt(protoMessageBytes.length);
// write attached value size, 4 byte
int valueLen = 0;
if (value != null) {
valueLen = value.length;
}
dos.writeInt(valueLen);
dos.flush();
// 9 byte header
byte[] beader = baos.toByteArray();
dos.close();
baos.close();
// 1. write header bytes (9 bytes)
os.write(beader);
// 2. write protobuf message byte[]
os.write(protoMessageBytes);
// 3 (optional) write attached value if any
if (valueLen > 0) {
// write value
os.write(value);
}
os.flush();
} catch (IOException e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
try {
this.isRunning = false;
this.mservice.close();
this.is.close();
this.dis.close();
this.os.close();
this.socket.close();
logger.info("tcp non-nio transport closed ...");
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
@Override
public void run() {
while (this.isRunning) {
try {
Message message = null;
KineticMessage km = new KineticMessage();
// 1. Read magic number.
int magicNumber = dis.readByte();
if (magicNumber != 'F') {
throw new CorruptedFrameException("Invalid magic number: "
+ magicNumber);
}
// 2. protobuf message size
int protoMessageLength = dis.readInt();
// 3. attched value size
int attachedValueLength = dis.readInt();
// 4. read protobuf message
byte[] decoded = new byte[protoMessageLength];
dis.read(decoded);
// construct protobuf message
Message.Builder builder = Message.newBuilder();
try {
builder.mergeFrom(decoded);
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw new RuntimeException(e);
}
// 5. read attched value if any
if (attachedValueLength > 0) {
// construct byte[]
byte[] attachedValue = new byte[attachedValueLength];
// read from buffer
dis.read(attachedValue);
// set to message
// builder.setValue(ByteString.copyFrom(attachedValue));
km.setValue(attachedValue);
}
message = builder.build();
// set message to kietic message
km.setMessage(message);
// build command
Command.Builder commandBuilder = Command.newBuilder();
try {
commandBuilder.mergeFrom(message.getCommandBytes());
km.setCommand(commandBuilder.build());
} catch (InvalidProtocolBufferException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
if (logger.isLoggable(Level.FINEST)) {
logger.finest("read message: " + message);
}
this.mservice.routeMessage(km);
} catch (Exception e) {
this.isRunning = false;
}
}
}
}