Reworked how client connection teardown happens in the face

of the network connection disappearing or any other random errors
on the channel.  Now errors that cause the connection to drop
will be properly reported as client disconnects... there is also
a new error field on the DisconnectInfo that is filled in in these
cases.
Added an ErrorListener that can be used to more tightly control
how errors are handled for the Client.
Fixed the double event dispatch that occurred during Client
closing.


git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7451 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
PSp..om 14 years ago
parent 95315cfd0c
commit 8ed2db29a6
  1. 13
      engine/src/networking/com/jme3/network/Client.java
  2. 1
      engine/src/networking/com/jme3/network/ClientStateListener.java
  3. 45
      engine/src/networking/com/jme3/network/ErrorListener.java
  4. 65
      engine/src/networking/com/jme3/network/base/ConnectorAdapter.java
  5. 63
      engine/src/networking/com/jme3/network/base/DefaultClient.java

@ -119,6 +119,19 @@ public interface Client extends MessageConnection
*/
public void removeMessageListener( MessageListener<? super Client> listener, Class... classes );
/**
* Adds a listener that will be notified when any connection errors
* occur. If a client has no error listeners then the default behavior
* is to close the connection and provide an appropriate DisconnectInfo
* to any ClientStateListeners. If the application adds its own error
* listeners then it must take care of closing the connection itself.
*/
public void addErrorListener( ErrorListener<? super Client> listener );
/**
* Removes a previously registered error listener.
*/
public void removeErrorListener( ErrorListener<? super Client> listener );
}

@ -63,5 +63,6 @@ public interface ClientStateListener
public class DisconnectInfo
{
public String reason;
public Throwable error;
}
}

@ -0,0 +1,45 @@
/*
* Copyright (c) 2011 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.network;
/**
* Notified when errors happen on a connection.
*
* @version $Revision$
* @author Paul Speed
*/
public interface ErrorListener<S>
{
public void handleError( S source, Throwable t );
}

@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import com.jme3.network.ErrorListener;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.kernel.Connector;
@ -65,6 +66,7 @@ public class ConnectorAdapter extends Thread
{
private Connector connector;
private MessageListener<Object> dispatcher;
private ErrorListener<Object> errorHandler;
private AtomicBoolean go = new AtomicBoolean(true);
// Writes messages out on a background thread
@ -74,11 +76,13 @@ public class ConnectorAdapter extends Thread
// through this connector.
private boolean reliable;
public ConnectorAdapter( Connector connector, MessageListener<Object> dispatcher, boolean reliable )
public ConnectorAdapter( Connector connector, MessageListener<Object> dispatcher,
ErrorListener<Object> errorHandler, boolean reliable )
{
super( String.valueOf(connector) );
this.connector = connector;
this.dispatcher = dispatcher;
this.errorHandler = errorHandler;
this.reliable = reliable;
setDaemon(true);
writer = Executors.newFixedThreadPool(1,
@ -106,30 +110,42 @@ public class ConnectorAdapter extends Thread
writer.execute( new MessageWriter(data) );
}
protected void handleError( Exception e )
{
if( !go.get() )
return;
errorHandler.handleError( this, e );
}
public void run()
{
MessageProtocol protocol = new MessageProtocol();
while( go.get() ) {
ByteBuffer buffer = connector.read();
if( buffer == null ) {
if( go.get() ) {
throw new ConnectorException( "Connector closed." );
} else {
// Just dump out because a null buffer is expected
// from a closed/closing connector
break;
try {
while( go.get() ) {
ByteBuffer buffer = connector.read();
if( buffer == null ) {
if( go.get() ) {
throw new ConnectorException( "Connector closed." );
} else {
// Just dump out because a null buffer is expected
// from a closed/closing connector
break;
}
}
protocol.addBuffer( buffer );
Message m = null;
while( (m = protocol.getMessage()) != null ) {
m.setReliable( reliable );
dispatch( m );
}
}
protocol.addBuffer( buffer );
Message m = null;
while( (m = protocol.getMessage()) != null ) {
m.setReliable( reliable );
dispatch( m );
}
}
} catch( Exception e ) {
handleError( e );
}
}
protected class MessageWriter implements Runnable
@ -144,8 +160,13 @@ public class ConnectorAdapter extends Thread
public void run()
{
if( !go.get() )
return;
connector.write(data);
return;
try {
connector.write(data);
} catch( Exception e ) {
handleError( e );
}
}
}

@ -68,6 +68,7 @@ public class DefaultClient implements Client
private Connector fast;
private MessageListenerRegistry<Client> messageListeners = new MessageListenerRegistry<Client>();
private List<ClientStateListener> stateListeners = new CopyOnWriteArrayList<ClientStateListener>();
private List<ErrorListener<? super Client>> errorListeners = new CopyOnWriteArrayList<ErrorListener<? super Client>>();
private Redispatch dispatcher = new Redispatch();
private ConnectorAdapter reliableAdapter;
private ConnectorAdapter fastAdapter;
@ -93,9 +94,9 @@ public class DefaultClient implements Client
this.reliable = reliable;
this.fast = fast;
reliableAdapter = new ConnectorAdapter(reliable, dispatcher, true);
reliableAdapter = new ConnectorAdapter(reliable, dispatcher, dispatcher, true);
if( fast != null ) {
fastAdapter = new ConnectorAdapter(fast, dispatcher, false);
fastAdapter = new ConnectorAdapter(fast, dispatcher, dispatcher, false);
}
}
@ -230,7 +231,15 @@ public class DefaultClient implements Client
public void close()
{
checkRunning();
closeConnections( null );
}
protected void closeConnections( DisconnectInfo info )
{
if( !isRunning )
return;
// Send a close message
// Tell the thread it's ok to die
@ -246,7 +255,7 @@ public class DefaultClient implements Client
// Just in case we never fully connected
connecting.countDown();
fireDisconnected(null);
fireDisconnected(info);
isRunning = false;
}
@ -280,6 +289,16 @@ public class DefaultClient implements Client
{
messageListeners.removeMessageListener( listener, classes );
}
public void addErrorListener( ErrorListener<? super Client> listener )
{
errorListeners.add( listener );
}
public void removeErrorListener( ErrorListener<? super Client> listener )
{
errorListeners.remove( listener );
}
protected void fireConnected()
{
@ -295,6 +314,27 @@ public class DefaultClient implements Client
}
}
/**
* Either calls the ErrorListener or closes the connection
* if there are no listeners.
*/
protected void handleError( Throwable t )
{
// If there are no listeners then close the connection with
// a reason
if( errorListeners.isEmpty() ) {
DisconnectInfo info = new DisconnectInfo();
info.reason = "Connection Error";
info.error = t;
closeConnections(info);
return;
}
for( ErrorListener l : errorListeners ) {
l.handleError( this, t );
}
}
protected void dispatch( Message m )
{
// Pull off the connection management messages we're
@ -312,8 +352,7 @@ public class DefaultClient implements Client
log.log( Level.SEVERE, "Connection terminated, reason:{0}.", reason );
DisconnectInfo info = new DisconnectInfo();
info.reason = reason;
fireDisconnected(info);
close();
closeConnections(info);
}
// Make sure client MessageListeners are called single-threaded
@ -324,11 +363,19 @@ public class DefaultClient implements Client
}
}
protected class Redispatch implements MessageListener<Object>
protected class Redispatch implements MessageListener<Object>, ErrorListener<Object>
{
public void messageReceived( Object source, Message m )
{
dispatch( m );
}
}
public void handleError( Object source, Throwable t )
{
// Only doing the DefaultClient.this to make the code
// checker happy... it compiles fine without it but I
// don't like red lines in my editor. :P
DefaultClient.this.handleError( t );
}
}
}

Loading…
Cancel
Save