Home | History | Annotate | Download | only in base
      1 /*
      2  * Copyright (c) 2011 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.network.base;
     34 
     35 import com.jme3.network.Message;
     36 import com.jme3.network.serializing.Serializer;
     37 import java.io.IOException;
     38 import java.nio.ByteBuffer;
     39 import java.util.LinkedList;
     40 
     41 /**
     42  *  Consolidates the conversion of messages to/from byte buffers
     43  *  and provides a rolling message buffer.  ByteBuffers can be
     44  *  pushed in and messages will be extracted, accumulated, and
     45  *  available for retrieval.  This is not thread safe and is meant
     46  *  to be used within a single message processing thread.
     47  *
     48  *  <p>The protocol is based on a simple length + data format
     49  *  where two bytes represent the (short) length of the data
     50  *  and the rest is the raw data for the Serializers class.</p>
     51  *
     52  *  @version   $Revision: 8843 $
     53  *  @author    Paul Speed
     54  */
     55 public class MessageProtocol
     56 {
     57     private LinkedList<Message> messages = new LinkedList<Message>();
     58     private ByteBuffer current;
     59     private int size;
     60     private Byte carry;
     61 
     62     /**
     63      *  Converts a message to a ByteBuffer using the Serializer
     64      *  and the (short length) + data protocol.  If target is null
     65      *  then a 32k byte buffer will be created and filled.
     66      */
     67     public static ByteBuffer messageToBuffer( Message message, ByteBuffer target )
     68     {
     69         // Could let the caller pass their own in
     70         ByteBuffer buffer = target == null ? ByteBuffer.allocate( 32767 + 2 ) : target;
     71 
     72         try {
     73             buffer.position( 2 );
     74             Serializer.writeClassAndObject( buffer, message );
     75             buffer.flip();
     76             short dataLength = (short)(buffer.remaining() - 2);
     77             buffer.putShort( dataLength );
     78             buffer.position( 0 );
     79 
     80             return buffer;
     81         } catch( IOException e ) {
     82             throw new RuntimeException( "Error serializing message", e );
     83         }
     84     }
     85 
     86     /**
     87      *  Retrieves and removes an extracted message from the accumulated buffer
     88      *  or returns null if there are no more messages.
     89      */
     90     public Message getMessage()
     91     {
     92         if( messages.isEmpty() ) {
     93             return null;
     94         }
     95 
     96         return messages.removeFirst();
     97     }
     98 
     99     /**
    100      *  Adds the specified buffer, extracting the contained messages
    101      *  and making them available to getMessage().  The left over
    102      *  data is buffered to be combined with future data.
    103      &
    104      *  @return The total number of queued messages after this call.
    105      */
    106     public int addBuffer( ByteBuffer buffer )
    107     {
    108         // push the data from the buffer into as
    109         // many messages as we can
    110         while( buffer.remaining() > 0 ) {
    111 
    112             if( current == null ) {
    113 
    114                 // If we have a left over carry then we need to
    115                 // do manual processing to get the short value
    116                 if( carry != null ) {
    117                     byte high = carry;
    118                     byte low = buffer.get();
    119 
    120                     size = (high & 0xff) << 8 | (low & 0xff);
    121                     carry = null;
    122                 }
    123                 else if( buffer.remaining() < 2 ) {
    124                     // It's possible that the supplied buffer only has one
    125                     // byte in it... and in that case we will get an underflow
    126                     // when attempting to read the short below.
    127 
    128                     // It has to be 1 or we'd never get here... but one
    129                     // isn't enough so we stash it away.
    130                     carry = buffer.get();
    131                     break;
    132                 } else {
    133                     // We are not currently reading an object so
    134                     // grab the size.
    135                     // Note: this is somewhat limiting... int would
    136                     // be better.
    137                     size = buffer.getShort();
    138                 }
    139 
    140                 // Allocate the buffer into which we'll feed the
    141                 // data as we get it
    142                 current = ByteBuffer.allocate(size);
    143             }
    144 
    145             if( current.remaining() <= buffer.remaining() ) {
    146                 // We have at least one complete object so
    147                 // copy what we can into current, create a message,
    148                 // and then continue pulling from buffer.
    149 
    150                 // Artificially set the limit so we don't overflow
    151                 int extra = buffer.remaining() - current.remaining();
    152                 buffer.limit( buffer.position() + current.remaining() );
    153 
    154                 // Now copy the data
    155                 current.put( buffer );
    156                 current.flip();
    157 
    158                 // Now set the limit back to a good value
    159                 buffer.limit( buffer.position() + extra );
    160 
    161                 createMessage( current );
    162 
    163                 current = null;
    164             } else {
    165 
    166                 // Not yet a complete object so just copy what we have
    167                 current.put( buffer );
    168             }
    169         }
    170 
    171         return messages.size();
    172     }
    173 
    174     /**
    175      *  Creates a message from the properly sized byte buffer
    176      *  and adds it to the messages queue.
    177      */
    178     protected void createMessage( ByteBuffer buffer )
    179     {
    180         try {
    181             Object obj = Serializer.readClassAndObject( buffer );
    182             Message m = (Message)obj;
    183             messages.add(m);
    184         } catch( IOException e ) {
    185             throw new RuntimeException( "Error deserializing object", e );
    186         }
    187     }
    188 }
    189 
    190 
    191 
    192