/*
* 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 = Integer.getInteger("tookit.packet.max",2* 1024 * 1024);
protected static int AUTO_SHRINK_SIZE = Integer.getInteger("tookit.packet.shrink",10 * 1024);
private double average = INITIAL_BUFFER_CAPACITY;
private long times = 1;
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()) {
calculateAverage(this._length);
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 && _buffer.hasRemaining()){
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 > MAX_BUFFER_CAPACITY || _buffer.capacity() > 2 * MAX_BUFFER_CAPACITY){
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() < 2* MAX_BUFFER_CAPACITY);
if (checkForCompletePacket()) {
calculateAverage(this._length);
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()) {
calculateAverage(this._length);
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()) {
calculateAverage(this._length);
return readPacket();
}else{
return null;
}
}
protected 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;
}
}
protected void shrinkCapacity(){
boolean shrink = false;
int aver = (int )Math.ceil(average);
int skrinkSize = aver;
if(_buffer.capacity() > AUTO_SHRINK_SIZE){
shrink = true;
if(Runtime.getRuntime().freeMemory() < MAX_BUFFER_CAPACITY){
skrinkSize = INITIAL_BUFFER_CAPACITY;
}else{
skrinkSize = (int)aver*2 > AUTO_SHRINK_SIZE? AUTO_SHRINK_SIZE:(int)aver*2;
}
}else if(_buffer.capacity() <= AUTO_SHRINK_SIZE){
if(_buffer.capacity() > aver * 4){
shrink = true;
skrinkSize = (int)aver * 2;
}
}
if(shrink && skrinkSize < MAX_BUFFER_CAPACITY && skrinkSize > _have){
//System.out.println("hashcode="+this.hashCode()+",capacity="+_buffer.capacity()+",shrink="+shrinkSize+",average="+aver);
ByteBuffer newbuf = ByteBuffer.allocate(skrinkSize);
newbuf.put((ByteBuffer)_buffer.flip());
_buffer = newbuf;
}
}
protected void calculateAverage(int current){
double j = (double) times/ (double)(times +1);
double x = (double)average * j + (double)current/ (double)(times +1);
average = x;
times ++;
}
/**
* 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();
}