/* * 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(); }