org.coos.messaging.transport.NioTCPTransport Maven / Gradle / Ivy
/**
* COOS - Connected Objects Operating System (www.connectedobjects.org).
*
* Copyright (C) 2009 Telenor ASA and Tellu AS. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This library 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 program. If not, see .
*
* You may also contact one of the following for additional information:
* Telenor ASA, Snaroyveien 30, N-1331 Fornebu, Norway (www.telenor.no)
* Tellu AS, Hagalokkveien 13, N-1383 Asker, Norway (www.tellu.no)
*/
package org.coos.messaging.transport;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.coos.messaging.Channel;
import org.coos.messaging.Message;
import org.coos.messaging.Processor;
import org.coos.messaging.ProcessorException;
import org.coos.messaging.Service;
import org.coos.messaging.Transport;
import org.coos.messaging.impl.DefaultMessage;
import org.coos.messaging.impl.DefaultProcessor;
/**
* Represents one non-blocking tcp connection. Decodes messages by reading the
* length from the first 4 bytes.
*
* @author Morten Versvik, Tellu AS
*
*/
public class NioTCPTransport extends DefaultProcessor implements Transport, Service {
/**
* Initial buffer size.
*/
private final static int BUFFER_SIZE = 8 * 1024;
/**
* 1 int is 4 bytes.
*/
private static final int SIZE_POSITION = 4 - 1;
protected List mailbox = Collections.synchronizedList(new LinkedList());
protected Processor transportProcessor;
private ByteBuffer outBuffer = null, inBuffer;
SocketChannel sc;
NioTCPTransportManager tm;
private byte[] buffer = new byte[BUFFER_SIZE];
private int pos = 0;
private int newmsgLength = Integer.MAX_VALUE;
protected int MAX_LENGTH = 8 * 1024; // Max length for a coos msg
boolean partialRead = false;
public NioTCPTransport(NioTCPTransportManager tm, Selector selector, SocketChannel sc) throws IOException {
this.sc = sc;
inBuffer = ByteBuffer.allocateDirect(sc.socket().getReceiveBufferSize());
this.tm = tm;
}
@Override public void setChainedProcessor(Processor chainedProcessor) {
this.transportProcessor = chainedProcessor;
}
public void receivedMessage(Message in) {
try {
transportProcessor.processMessage(in);
} catch (ProcessorException e) {
logger.error("ProcessorException ignored.", e);
}
}
public int getMailboxSize() {
return mailbox.size();
}
public Message getMessage() {
return mailbox.remove(0);
}
@Override public void setChannel(Channel channel) {
}
@Override public void processMessage(Message msg) throws ProcessorException {
String priStr = msg.getHeader(Message.PRIORITY);
if (priStr != null) {
int pri = Integer.valueOf(priStr);
int idx = 0;
for (Message message : mailbox) {
String pr = message.getHeader(Message.PRIORITY);
if (pr != null) {
int p = Integer.valueOf(pr);
if (pri < p) {
mailbox.add(idx, msg);
synchronized (this) {
this.notify();
}
break;
}
}
idx++;
}
} else {
mailbox.add(msg);
synchronized (this) {
this.notify();
}
}
// A new message was added, notify the writer.
if (sc != null)
tm.readyWrite(sc);
}
@Override public void start() throws Exception {
}
@Override public void stop() throws Exception {
}
public byte[] decode(ByteBuffer socketBuffer) throws IOException {
// Reads until the buffer is empty or until a packet
// is fully reassembled.
while (socketBuffer.hasRemaining()) {
// Copies into the temporary buffer
byte data = socketBuffer.get();
try {
buffer[pos] = data;
if (pos == SIZE_POSITION) { // Got size parameter
ByteArrayInputStream bin = new ByteArrayInputStream(buffer);
DataInputStream din = new DataInputStream(bin);
newmsgLength = din.readInt() + SIZE_POSITION + 1;
if (newmsgLength > MAX_LENGTH)
throw new IOException("Packet too big");
if (buffer.length < newmsgLength)
buffer = new byte[newmsgLength];
}
} catch (IndexOutOfBoundsException e) {
// We resize the buffer, shouldn't happen.
throw new IOException("Packet too big. Maximum size allowed: " + BUFFER_SIZE + " bytes.", e);
}
pos++;
// Check if it is the final byte of a packet.
if (pos == newmsgLength) {
newmsgLength = Integer.MAX_VALUE;
// The current packet is fully reassembled. Return it
byte[] newBuffer = new byte[pos];
System.arraycopy(buffer, 0, newBuffer, 0, pos);
pos = 0;
return newBuffer;
}
}
// No packet was reassembled. There is not enough data. Wait
// for more data to arrive.
return null;
}
/**
* Reads and decodes bytes from socket
* @return -1 disconnected, 0 = 0 read, 1 = >= 1 bytes read
* @throws Exception
*/
public int decodeFromSocket() throws Exception {
if (!partialRead) {
int readBytes = sc.read(inBuffer);
if (readBytes == -1) {
sc.close();
tm.socketDisconnected(sc);
return -1;
}
if ((readBytes == 0) && (inBuffer.position() == 0)) {
return 0;
}
inBuffer.flip();
}
byte[] bb = decode(inBuffer);
if (bb == null) {
// Partial packet received. Must wait for more data. All the
// contents
// of inBuffer were processed by the protocol decoder. We can
// delete it and prepare for more data.
inBuffer.clear();
partialRead = false;
} else {
// A packet was reassembled.
Message msg;
try {
// System.out.println(bb.length);
msg = new DefaultMessage(new DataInputStream(new ByteArrayInputStream(bb)));
// System.out.println(msg);
receivedMessage(msg);
partialRead = true;
} catch (Exception e) {
logger.warn("Exception ignored", e);
}
// The inBuffer might still have some data left. Perhaps
// the beginning of another packet. So don't clear it. Next
// time reading is activated, we start by processing the inBuffer
// again.
}
return 1;
}
public void handleWrite() throws Exception {
if (outBuffer == null) {
if (mailbox.size() == 0) {
tm.doneWrite(sc);
return;
}
Message msg = getMessage();
outBuffer = ByteBuffer.wrap(msg.serialize());
}
sc.write(outBuffer);
// Check if there are more to be written.
if (!outBuffer.hasRemaining()) {
// outBuffer was completely written, deactivate write intent.
outBuffer = null;
tm.doneWrite(sc);
}
}
}