/*
* This program is free software; you can redistribute it and/or modify it under the terms of
* the GNU AFFERO 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 AFFERO GENERAL PUBLIC LICENSE for more details.
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.meidusa.amoeba.net.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import com.meidusa.amoeba.util.StringUtil;
/**
*
*
* @author <a href=mailto:piratebase@sina.com>Struct chen</a>
*
*/
public abstract class PacketInputStream {
/**
* 用于读取可读通道的缓存对象,初始化容量={@link #INITIAL_BUFFER_CAPACITY},不够时候每次增长上一次的一倍
*/
protected ByteBuffer _buffer;
/** 整个封包长度,包括包头 */
protected int _length = -1;
/**
* 当前buffer 中的字节长度
*/
protected int _have = 0;
/**
* 初始化buffer 容量,默认32字节
*/
protected static final int INITIAL_BUFFER_CAPACITY = 32;
/** 最大容量 */
protected static final int MAX_BUFFER_CAPACITY = 1024 * 1024 * 2;
private int maxPacketSize = MAX_BUFFER_CAPACITY;
public int getMaxPacketSize() {
return maxPacketSize;
}
public void setMaxPacketSize(int maxPacketSize) {
this.maxPacketSize = maxPacketSize;
}
private byte[] tmp = new byte[4096];
/**
* Creates a new framed input stream.
*/
public PacketInputStream ()
{
_buffer = ByteBuffer.allocate(INITIAL_BUFFER_CAPACITY);
}
/**
* Reads a packet from the provided channel, appending to any partially
* read packet. If the entire packet data is not yet available,
* <code>readPacket</code> will return false, otherwise true.
*
* <p> <em>Note:</em> when this method returns true, it is required
* that the caller read <em>all</em> of the packet data from the stream
* before again calling {@link #readPacket} as the previous packet's
* data will be elimitated upon the subsequent call.
*
* @return byte[] if the entire packet has been read, null if the buffer contains only a partial packet.
*/
public byte[] readPacket(ReadableByteChannel source)
throws IOException
{
if (checkForCompletePacket()) {
return readPacket();
}
// read whatever data we can from the source
do {
int got = source.read(_buffer);
if (got == -1) {
throw new EOFException();
}
if(got == 0){
return null;
}
_have += got;
if (_length == -1) {
// if we didn't already have our length, see if we now
// have enough data to obtain it
_length = decodeLength();
}
if(_length < -1){
_buffer.flip();
byte[] bts = _buffer.array();
throw new IOException("decodeLength error:_length="+_length+"\r\n"+StringUtil.dumpAsHex(bts, bts.length));
}
// if there's room remaining in the buffer, that means we've
// read all there is to read, so we can move on to inspecting
// what we've got
if (_buffer.remaining() > 0) {
break;
}
// additionally, if the buffer happened to be exactly as long
// as we needed, we need to break as well
if ((_length > 0) && (_have >= _length)) {
break;
}
// otherwise, we've filled up our buffer as a result of this
// read, expand it and try reading some more
int newSize = _buffer.capacity() << 1;
newSize = newSize>_length ? newSize:_length+16;
if(newSize > maxPacketSize){
throw new IOException("packet over MAX_BUFFER_CAPACITY size="+newSize);
}
ByteBuffer newbuf = ByteBuffer.allocate(newSize);
newbuf.put((ByteBuffer)_buffer.flip());
_buffer = newbuf;
// don't let things grow without bounds
} while (_buffer.capacity() < maxPacketSize);
if (checkForCompletePacket()) {
return readPacket();
}else{
return null;
}
}
protected abstract byte[] readPacket();
/**
* only for bio
* @param source
* @return
* @throws IOException
*/
public byte[] readPacket (InputStream source)
throws IOException
{
// we may already have the next frame entirely in the buffer from
// a previous read
if (checkForCompletePacket()) {
return readPacket();
}
// read whatever data we can from the source
do {
int got = source.read(tmp);
expandCapacity(got);
if(got ==0) return null;
if(got >0){
//got = source.read(byt);
this._buffer.put(tmp, 0, got);
}
if (got == -1) {
throw new EOFException();
}
_have += got;
if (_length == -1) {
// if we didn't already have our length, see if we now
// have enough data to obtain it
_length = decodeLength();
}
if(_length<-1 || _length > MAX_BUFFER_CAPACITY){
throw new IOException("over max packet limit");
}
// don't let things grow without bounds
} while (_buffer.capacity() < MAX_BUFFER_CAPACITY && !checkForCompletePacket());
if (checkForCompletePacket()) {
return readPacket();
}else{
return null;
}
}
private void expandCapacity(int needSize){
if(_buffer.remaining()<needSize){
int newSize = _buffer.capacity() << 1;
newSize = newSize>_length ? newSize:_length+16;
ByteBuffer newbuf = ByteBuffer.allocate(newSize>needSize?newSize:needSize);
newbuf.put((ByteBuffer)_buffer.flip());
_buffer = newbuf;
}
}
/**
* Decodes and returns the length of the current packet from the buffer
* if possible. Returns -1 otherwise.
*/
protected abstract int decodeLength ();
/**
* Returns true if a complete frame is in the buffer, false otherwise.
* If a complete packet is in the buffer, the buffer will be prepared
* to deliver that frame via our {@link InputStream} interface.
*/
protected boolean checkForCompletePacket ()
{
if (_length <= -1 || _have < _length || _length < getHeaderSize()) {
return false;
}
return true;
}
public String toString(){
StringBuffer buffer = new StringBuffer();
buffer.append("buffer:").append(_buffer).append(",length:").append(_length).append(",have:").append(_have);
return buffer.toString();
}
/**
*
* @return packet header size
*/
public abstract int getHeaderSize();
}