[skycastle-commits] SF.net SVN: skycastle: [443] trunk/skycastle/modules

  • From: zzorn@xxxxxxxxxxxxxxxxxxxxx
  • To: skycastle-commits@xxxxxxxxxxxxx
  • Date: Sat, 05 Apr 2008 14:40:13 -0700

Revision: 443
          http://skycastle.svn.sourceforge.net/skycastle/?rev=443&view=rev
Author:   zzorn
Date:     2008-04-05 14:40:12 -0700 (Sat, 05 Apr 2008)

Log Message:
-----------
Extracted common protocol negotiation and message decoding and encoding logic 
from the server, which can also be used for the client.

Modified Paths:
--------------
    
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java
    
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java
    
trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java

Added Paths:
-----------
    
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java

Added: 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java
===================================================================
--- 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java
                         (rev 0)
+++ 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java
 2008-04-05 21:40:12 UTC (rev 443)
@@ -0,0 +1,249 @@
+package org.skycastle.protocol;
+
+import org.skycastle.messaging.Message;
+import org.skycastle.protocol.negotiation.NegotiationStatus;
+import org.skycastle.protocol.negotiation.ProtocolNegotiator;
+import org.skycastle.util.ParameterChecker;
+
+import java.io.Serializable;
+
+/**
+ * Takes care of negotiating a protocol, and after that forwarding decoded 
messages to a listener.
+ * <p/>
+ * Also notifies the listener when a protocol has been negotiated, or 
negotiations failed.
+ * <p/>
+ * Call startNegotiations to start the negotiation process.
+ *
+ * @author Hans Häggström
+ */
+public abstract class ProtocolCommunicator
+        implements Serializable
+{
+
+    //======================================================================
+    // Private Fields
+
+    private final ProtocolNegotiator myProtocolNegotiator;
+
+    private int myProtocolNegotiationTimeout_ms;
+    private boolean myNegotiationsStarted = false;
+
+    //======================================================================
+    // Private Constants
+
+    private static final long serialVersionUID = 1L;
+
+    //======================================================================
+    // Public Methods
+
+    //----------------------------------------------------------------------
+    // Constructors
+
+    /**
+     * Creates a new {@link org.skycastle.protocol.ProtocolCommunicator}.
+     * <p/>
+     * Call startNegotiations to start the negotiation process.
+     *
+     * @param protocolNegotiator            the negotiator to use to determine 
the protocol.  Different for
+     *                                      server and client side.
+     * @param protocolNegotiationTimeout_ms the timeout in milliseconds after 
which protocol negotiation is
+     *                                      aborted and considered failed.
+     */
+    public ProtocolCommunicator( final ProtocolNegotiator protocolNegotiator,
+                                 final int protocolNegotiationTimeout_ms )
+    {
+        myProtocolNegotiationTimeout_ms = protocolNegotiationTimeout_ms;
+        ParameterChecker.checkNotNull( protocolNegotiator, 
"protocolNegotiator" );
+        ParameterChecker.checkPositiveNonZeroInteger( 
protocolNegotiationTimeout_ms,
+                                                      
"protocolNegotiationTimeout_ms" );
+
+        myProtocolNegotiator = protocolNegotiator;
+    }
+
+    //----------------------------------------------------------------------
+    // Other Public Methods
+
+    /**
+     * Should be called when everything is ready to start the negotiations 
(the implementing class is
+     * initialized).
+     * <p/>
+     * Starts the negotiation timeout, and sends opening message to the other 
party (depending on the {@link
+     * ProtocolNegotiator} type).
+     */
+    public final void startNegotiations()
+    {
+        if ( myNegotiationsStarted )
+        {
+            throw new IllegalStateException(
+                    "Negotiations have already been started once, can not be 
started twice." );
+        }
+        else
+        {
+            myNegotiationsStarted = true;
+
+            // Give ourselves a deadline until the protocol should be 
negotiated
+            scheduleTimeoutCallback( myProtocolNegotiationTimeout_ms );
+
+            // If this side should start protocol negotiations, send the 
opening message
+            if ( myProtocolNegotiator.startsNegotiations() )
+            {
+                sendEncodedMessage( myProtocolNegotiator.handleMessage( null ) 
);
+            }
+        }
+    }
+
+
+    /**
+     * Should be called after the time specified amount of time after 
scheduleTimeoutCallback is called. Used
+     * to check if the protocol negotiation timed out.
+     */
+    public final void timeoutCallback()
+    {
+        if ( !myProtocolNegotiator.getStatus().isFinished() )
+        {
+            myProtocolNegotiator.timeoutCheck();
+
+            checkForNegotiationFinish();
+        }
+    }
+
+
+    /**
+     * @return true when a protocol has been agreed upon, and messages can be 
sent and recieved to/from the
+     *         other party.
+     */
+    public final boolean canSendMessages()
+    {
+        return myProtocolNegotiator.getStatus().isSuccess();
+    }
+
+
+    /**
+     * Handles incoming messages from the other party.
+     *
+     * @throws ProtocolException if there was a problem when decoding this 
message.
+     */
+    public final void handleReceivedEncodedMessage( final byte[] byteMessage )
+            throws ProtocolException
+    {
+        final NegotiationStatus status = myProtocolNegotiator.getStatus();
+
+        if ( status.isSuccess() )
+        {
+            handleMessage( byteMessage );
+        }
+        else if ( status.isFailure() )
+        {
+            throw new IllegalStateException( "Protocol negotiations failed, no 
more messages can be handled." );
+        }
+        else
+        {
+            doNegotiationRound( byteMessage );
+        }
+    }
+
+
+    /**
+     * Sends the specified message to the other party.
+     *
+     * @param message Message to send.  Should not be null.
+     *
+     * @throws ProtocolException thrown if there was some problem in encoding 
the message.
+     */
+    public final void sendMessage( final Message message )
+            throws ProtocolException
+    {
+        ParameterChecker.checkNotNull( message, "message" );
+
+        if ( myProtocolNegotiator.getStatus().isSuccess() )
+        {
+            // Encode message and send to other party
+            sendEncodedMessage( myProtocolNegotiator.getProtocol().encode( 
message ) );
+        }
+        else
+        {
+            throw new IllegalStateException(
+                    "No message can be sent when the protocol negotiation 
status is '" + myProtocolNegotiator.getStatus() + "' " );
+        }
+    }
+
+    //======================================================================
+    // Protected Methods
+
+    //----------------------------------------------------------------------
+    // Abstract Protected Methods
+
+    /**
+     * Should set up a timer to call the timeoutCallback after the specified 
time. Used to implement protocol
+     * negotiation timeout.
+     *
+     * @param timeout_ms number of milliseconds to wait before calling 
timeoutCallback.
+     */
+    protected abstract void scheduleTimeoutCallback( final long timeout_ms );
+
+    /**
+     * Called when a byte array message should be sent to the other party.
+     */
+    protected abstract void sendEncodedMessage( final byte[] encodedMessage );
+
+    /**
+     * Called if protocol negotiations failed for some reason.
+     *
+     * @param status a more detailed explanation of why the protocol 
negotiations failed.
+     */
+    protected abstract void onProtocolNegotiationFailed( final 
NegotiationStatus status );
+
+    /**
+     * Called if a protocol could be agreed to.  Now it is possible to send 
and recieve messages from the
+     * other party.
+     *
+     * @param protocolId the String id of the selected protocol, just for 
information.
+     */
+    protected abstract void onProtocolNegotiationSucceeded( final String 
protocolId );
+
+    /**
+     * Called when a message is recieved from the other party.
+     *
+     * @param message the deocoded message.
+     */
+    protected abstract void onMessage( final Message message ) throws 
ProtocolException;
+
+    //======================================================================
+    // Private Methods
+
+    private void doNegotiationRound( final byte[] byteMessage )
+    {
+        final byte[] reply = myProtocolNegotiator.handleMessage( byteMessage );
+
+        if ( reply != null )
+        {
+            sendEncodedMessage( reply );
+        }
+
+        checkForNegotiationFinish();
+    }
+
+
+    private void checkForNegotiationFinish()
+    {
+        if ( myProtocolNegotiator.getStatus().isFinished() )
+        {
+            if ( myProtocolNegotiator.getStatus().isSuccess() )
+            {
+                onProtocolNegotiationSucceeded( 
myProtocolNegotiator.getProtocol().getProtocolId() );
+            }
+            else
+            {
+                onProtocolNegotiationFailed( myProtocolNegotiator.getStatus() 
);
+            }
+        }
+    }
+
+
+    private void handleMessage( final byte[] byteMessage ) throws 
ProtocolException
+    {
+        // Decode incoming message and notify decendant implementation
+        onMessage( myProtocolNegotiator.getProtocol().decode( byteMessage ) );
+    }
+
+}

Modified: 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java
===================================================================
--- 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java
       2008-04-05 20:01:46 UTC (rev 442)
+++ 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java
       2008-04-05 21:40:12 UTC (rev 443)
@@ -86,6 +86,14 @@
         return myProtocol;
     }
 
+    public final void timeoutCheck()
+    {
+        if ( !myStatus.isFinished() )
+        {
+            myStatus = NegotiationStatus.TIMEOUT;
+        }
+    }
+
     //======================================================================
     // Protected Methods
 

Modified: 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java
===================================================================
--- 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java
       2008-04-05 20:01:46 UTC (rev 442)
+++ 
trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java
       2008-04-05 21:40:12 UTC (rev 443)
@@ -41,4 +41,10 @@
      *         the other party.
      */
     boolean startsNegotiations();
+
+    /**
+     * Notifies the {@link ProtocolNegotiator} that it has ran out of time, 
and should change its status to
+     * timeout failure, if it has not yet managed to negotiate a protocol.
+     */
+    void timeoutCheck();
 }

Modified: 
trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java
===================================================================
--- 
trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java
        2008-04-05 20:01:46 UTC (rev 442)
+++ 
trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java
        2008-04-05 21:40:12 UTC (rev 443)
@@ -1,19 +1,16 @@
 package org.skycastle.server;
 
-import com.sun.sgs.app.AppContext;
-import com.sun.sgs.app.ClientSession;
-import com.sun.sgs.app.ClientSessionListener;
-import com.sun.sgs.app.DataManager;
+import com.sun.sgs.app.*;
 import org.skycastle.core.*;
 import org.skycastle.messaging.Message;
 import org.skycastle.messaging.modifications.ModificationMessage;
 import org.skycastle.messaging.updates.UpdateMessage;
+import org.skycastle.protocol.ProtocolCommunicator;
 import org.skycastle.protocol.ProtocolException;
-import org.skycastle.protocol.negotiation.ProtocolNegotiator;
+import org.skycastle.protocol.negotiation.NegotiationStatus;
 import org.skycastle.protocol.negotiation.ServerSideProtocolNegotiator;
 import org.skycastle.protocol.registry.ProtocolRegistry;
 
-import java.io.Serializable;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -21,7 +18,8 @@
  * Initializes a client session and listens to messages (commands) and session 
events from the client.
  */
 public class SkycastleClientSessionHandler
-        implements Serializable, ClientSessionListener
+        extends ProtocolCommunicator
+        implements ClientSessionListener, Task
 {
 
     //======================================================================
@@ -35,8 +33,6 @@
     @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } )
     private final ClientSession myClientSession;
 
-    private final ProtocolNegotiator myProtocolNegotiator;
-
     // NOTE: For some reason the ManagedReference interface is not 
serializable, but the implementation is,
     // so we supress a serialization warning for IDE:s that look for those 
problems.
     @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } )
@@ -47,6 +43,8 @@
     //======================================================================
     // Private Constants
 
+    private static final int PROTOCOL_NEGOTIATION_TIMEOUT_MS = 10000;
+
     /**
      * The {@link Logger} for this class.
      */
@@ -75,50 +73,34 @@
     public SkycastleClientSessionHandler( final ClientSession clientSession,
                                           final 
PersistentReference<ProtocolRegistry> protocolRegistryReference )
     {
+        super( new ServerSideProtocolNegotiator( protocolRegistryReference,
+                                                 SERVER_TYPE,
+                                                 SERVER_VERSION ),
+               PROTOCOL_NEGOTIATION_TIMEOUT_MS );
+
         myClientSession = clientSession;
 
-        myProtocolNegotiator = new ServerSideProtocolNegotiator( 
protocolRegistryReference,
-                                                                 SERVER_TYPE,
-                                                                 
SERVER_VERSION );
-
-        // If the server should start protocol negotiations, have it send the 
opening message
-        if ( myProtocolNegotiator.startsNegotiations() )
-        {
-            clientSession.send( myProtocolNegotiator.handleMessage( null ) );
-        }
+        startNegotiations();
     }
 
     //----------------------------------------------------------------------
     // ClientSessionListener Implementation
 
     /**
-     * {@inheritDoc}
-     * <p/>
-     * Logs when data arrives from the client, and echoes the message back.
+     * Handles incoming messages from the client.
      */
     public void receivedMessage( final byte[] byteMessage )
     {
-        if ( myProtocolNegotiator.getStatus().isFinished() )
+        try
         {
-            if ( myProtocolNegotiator.getStatus().isSuccess() )
-            {
-                handleMessage( byteMessage );
-            }
+            handleReceivedEncodedMessage( byteMessage );
         }
-        else
+        catch ( ProtocolException e )
         {
-            // Continue protocol negotiation
-            final byte[] reply = myProtocolNegotiator.handleMessage( 
byteMessage );
+            // Log exception
+            LOGGER.log( Level.WARNING, "Problem when decoding message from a 
client: " + e.getMessage(), e );
 
-            if ( reply != null )
-            {
-                myClientSession.send( reply );
-            }
-
-            if ( myProtocolNegotiator.getStatus().isFinished() )
-            {
-                onProtocolNegotiationFinished( 
myProtocolNegotiator.getStatus().isSuccess() );
-            }
+            // TODO: Terminate connection in case of exceptions?
         }
     }
 
@@ -132,87 +114,106 @@
     {
         // TODO: Notify the users account that the user disconnected (the 
avatars can go into off-line mode, etc).
 
-        // DEBUG:
         final String grace = graceful ? "graceful" : "forced";
         LOGGER.log( Level.INFO,
-                    "User {0} has logged out {1}",
+                    "User {0} has done a {1} logout.",
                     new Object[]{ myClientSession.getName(), grace }
         );
     }
 
+    //----------------------------------------------------------------------
+    // Task Implementation
+
+
+    public void run() throws Exception
+    {
+        timeoutCallback();
+    }
+
     //======================================================================
-    // Private Methods
+    // Protected Methods
 
-    private void handleMessage( final byte[] byteMessage )
+    @Override
+    protected void scheduleTimeoutCallback( final long timeout_ms )
     {
-        try
-        {
-            // Decode incoming message
-            final Message message = myProtocolNegotiator.getProtocol().decode( 
byteMessage );
+        // TODO: Check if a ClientSessionListener is considered to be a Task, 
or if it can be one?
+        AppContext.getTaskManager().scheduleTask( this, timeout_ms );
+    }
 
-            // Check the message sender id so that the client is not claiming 
to be someone else.
-            if ( !myClientAccountId.equals( message.getSenderId() ) )
-            {
-                throw new ProtocolException( "The client claimed to have the 
ID '" + message.getSenderId() +
-                                             "', while in reality the ID of 
the client account was '" + myClientAccountId + "'." );
-            }
 
+    @Override
+    protected void sendEncodedMessage( final byte[] encodedMessage )
+    {
+        myClientSession.send( encodedMessage );
+    }
 
-            if ( message instanceof ModificationMessage )
-            {
-                handleModificationMessage( message, (ModificationMessage) 
message );
-            }
-            else if ( message instanceof UpdateMessage )
-            {
-                throw new ProtocolException(
-                        "The client can not send UpdateMessages to the server. 
 Recieved a message of type: '" + message.getClass() + "'." );
-            }
-            else
-            {
-                throw new ProtocolException( "Unknown message type '" + 
message.getClass() + "'." );
-            }
-        }
-        catch ( ProtocolException e )
-        {
-            LOGGER.warning( "Problem when decoding message from a client: " + 
e.getMessage() );
 
-            // TODO: Log exception with client/account somehow?  Enable fast 
detection of errorneous or flooding DoS:in clients
-            e.printStackTrace();
-        }
+    @Override
+    protected void onProtocolNegotiationFailed( final NegotiationStatus status 
)
+    {
+        LOGGER.log( Level.INFO,
+                    "Protocol negotiation failed for user '{0}': {1}",
+                    new Object[]{ myClientSession.getName(), status.toString() 
} );
+
+        myClientSession.disconnect();
     }
 
 
-    private void handleModificationMessage( final Message message,
-                                            final ModificationMessage 
modificationMessage )
+    @Override
+    protected void onProtocolNegotiationSucceeded( final String protocolId )
     {
-        final GameObjectId targetId = modificationMessage.getTargetId();
-        final GameObject target = 
GameContext.getGameObjectContext().getGameObjectById(
-                targetId,
-                true );
+        LOGGER.log( Level.INFO,
+                    "User '{0}' logged in.",
+                    new Object[]{ myClientSession.getName() } );
 
-        target.onMessage( message );
+        final GameObject account = getUserAccount( myClientSession.getName() );
+
+        // TODO: Notify the account object that the user logged in.
+
+        myClientAccountReference = new GameObjectReference( account );
     }
 
 
-    private void onProtocolNegotiationFinished( final boolean success )
+    @Override
+    protected void onMessage( final Message message ) throws ProtocolException
     {
-        if ( success )
+        // Check the message sender id so that the client is not claiming to 
be someone else.
+        if ( !myClientAccountId.equals( message.getSenderId() ) )
         {
-            // If the protocol negotiation finished successfully, get the 
users account
-            final GameObject account = getUserAccount( 
myClientSession.getName() );
+            throw new ProtocolException( "The client claimed to have the ID '" 
+ message.getSenderId() +
+                                         "', while in reality the ID of the 
client account was '" + myClientAccountId + "'." );
+        }
 
-            // TODO: Notify the account object that the user logged in.
-
-            myClientAccountReference = new GameObjectReference( account );
+        if ( message instanceof ModificationMessage )
+        {
+            handleModificationMessage( message, (ModificationMessage) message 
);
         }
+        else if ( message instanceof UpdateMessage )
+        {
+            throw new ProtocolException(
+                    "The client can not send UpdateMessages to the server.  
Recieved a message of type: '" + message.getClass() + "'." );
+        }
         else
         {
-            // If negotiation failed with error, disconnect
-            myClientSession.disconnect();
+            throw new ProtocolException( "Unknown message type '" + 
message.getClass() + "'." );
         }
     }
 
+    //======================================================================
+    // Private Methods
 
+    private void handleModificationMessage( final Message message,
+                                            final ModificationMessage 
modificationMessage )
+    {
+        final GameObjectId targetId = modificationMessage.getTargetId();
+        final GameObject target = 
GameContext.getGameObjectContext().getGameObjectById(
+                targetId,
+                true );
+
+        target.onMessage( message );
+    }
+
+
     /**
      * Get users account.  The account will typically contain actions for 
resuming some existing avatars or
      * creating new avatars, and doing general account management and maybe 
out-of-game chat.


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

Other related posts:

  • » [skycastle-commits] SF.net SVN: skycastle: [443] trunk/skycastle/modules