--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * Represents a remote connection to a server that can be used
+ * for sending and receiving messages.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface Client extends MessageConnection
+{
+ /**
+ * Starts the client allowing it to begin processing incoming
+ * messages and delivering them to listeners.
+ */
+ public void start();
+
+ /**
+ * Returns true if this client is fully connected to the
+ * host.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns a unique ID for this client within the remote
+ * server or -1 if this client isn't fully connected to the
+ * server.
+ */
+ public long getId();
+
+ /**
+ * Sends a message to the server.
+ */
+ public void send( Message message );
+
+ /**
+ * Closes this connection to the server.
+ */
+ public void close();
+
+ /**
+ * Adds a listener that will be notified about connection
+ * state changes.
+ */
+ public void addClientStateListener( ClientStateListener listener );
+
+ /**
+ * Removes a previously registered connection listener.
+ */
+ public void removeClientStateListener( ClientStateListener listener );
+
+ /**
+ * Adds a listener that will be notified when any message or object
+ * is received from the server.
+ */
+ public void addMessageListener( MessageListener<? super Client> listener );
+
+ /**
+ * Adds a listener that will be notified when messages of the specified
+ * types are received.
+ */
+ public void addMessageListener( MessageListener<? super Client> listener, Class... classes );
+
+ /**
+ * Removes a previously registered wildcard listener. This does
+ * not remove this listener from any type-specific registrations.
+ */
+ public void removeMessageListener( MessageListener<? super Client> listener );
+
+ /**
+ * Removes a previously registered type-specific listener from
+ * the specified types.
+ */
+ public void removeMessageListener( MessageListener<? super Client> listener, Class... classes );
+
+}
+
+
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * Listener that is notified about the connection state of
+ * a Client.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface ClientStateListener
+{
+ /**
+ * Called when the specified client is fully connected to
+ * the remote server.
+ */
+ public void clientConnected( Client c );
+
+ /**
+ * Called when the client has disconnected from the remote
+ * server.
+ */
+ public void clientDisconnected( Client c );
+}
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * Listener that is notified about connection arrivals and
+ * removals within a server.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface ConnectionListener
+{
+ /**
+ * Called when a connection has been added to the specified server and
+ * is fully setup.
+ */
+ public void connectionAdded( Server server, HostedConnection conn );
+
+ /**
+ * Called when a connection has been removed from the specified
+ * server.
+ */
+ public void connectionRemoved( Server server, HostedConnection conn );
+}
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * This is the connection back to a client that is being
+ * hosted in a server instance.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface HostedConnection extends MessageConnection
+{
+ /**
+ * Returns the server-unique ID for this client.
+ */
+ public long getId();
+
+ /**
+ * Closes and removes this connection from the server
+ * sending the optional reason to the remote client.
+ */
+ public void close( String reason );
+}
--- /dev/null
+/*
+ * 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;
+
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Interface implemented by all network messages.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+@Serializable()
+public interface Message
+{
+ public boolean isReliable();
+}
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * The source of a received message and the common abstract interface
+ * of client->server and server->client objects.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface MessageConnection
+{
+ /**
+ * Sends a message to the other end of the connection.
+ */
+ public void send( Message message );
+}
+
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * Listener notified about new messages
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface MessageListener<S>
+{
+ public void messageReceived( S source, Message m );
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import com.jme3.network.base.DefaultClient;
+import com.jme3.network.base.DefaultServer;
+import com.jme3.network.kernel.tcp.SelectorKernel;
+import com.jme3.network.kernel.tcp.SocketConnector;
+import com.jme3.network.kernel.udp.UdpConnector;
+import com.jme3.network.kernel.udp.UdpKernel;
+
+/**
+ * The main service provider for conveniently creating
+ * server and client instances.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class Network
+{
+ /**
+ * Creates a Server that will utilize both reliable and fast
+ * transports to communicate with clients. The specified port
+ * will be used for both TCP and UDP communication.
+ */
+ public static Server createServer( int port ) throws IOException
+ {
+ InetAddress local = InetAddress.getLocalHost();
+
+ UdpKernel fast = new UdpKernel(local, port);
+ SelectorKernel reliable = new SelectorKernel(local,port);
+
+ return new DefaultServer( reliable, fast );
+ }
+
+ /**
+ * Creates a Client that communicates with the specified host and port
+ * using both reliable and fast transports. The localUdpPort specifies the
+ * local port to use for listening for incoming 'fast' UDP messages.
+ */
+ public static Client connectToServer( String host, int hostPort, int localUdpPort ) throws IOException
+ {
+ return connectToServer( InetAddress.getByName(host), hostPort, localUdpPort );
+ }
+
+ /**
+ * Creates a Client that communicates with the specified address and port
+ * using both reliable and fast transports. The localUdpPort specifies the
+ * local port to use for listening for incoming 'fast' messages.
+ */
+ public static Client connectToServer( InetAddress address, int port, int localUdpPort ) throws IOException
+ {
+ InetAddress local = InetAddress.getLocalHost();
+ UdpConnector fast = new UdpConnector( local, localUdpPort, address, port );
+ SocketConnector reliable = new SocketConnector( address, port );
+
+ return new DefaultClient( reliable, fast );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.util.Collection;
+
+/**
+ * Represents a host that can send and receive messages to
+ * a set of remote client connections.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public interface Server
+{
+ /**
+ * Sends the specified message to all connected clients.
+ */
+ public void broadcast( Message message );
+
+ /**
+ * Sends the specified message to all connected clients that match
+ * the filter.
+ */
+ public void broadcast( Object filter, Message message );
+
+ /**
+ * Start the server so that it will began accepting new connections
+ * and processing messages.
+ */
+ public void start();
+
+ /**
+ * Returns true if the server has been started.
+ */
+ public boolean isRunning();
+
+ /**
+ * Closes all client connections, stops and running processing threads, and
+ * closes the host connection.
+ */
+ public void close();
+
+ /**
+ * Retrieves a hosted connection by ID.
+ */
+ public HostedConnection getConnection( long id );
+
+ /**
+ * Retrieves a read-only collection of all currently connected connections.
+ */
+ public Collection<HostedConnection> getConnections();
+
+ /**
+ * Adds a listener that will be notified when new hosted connections
+ * arrive.
+ */
+ public void addConnectionListener( ConnectionListener listener );
+
+ /**
+ * Removes a previously registered connection listener.
+ */
+ public void removeConnectionListener( ConnectionListener listener );
+
+ /**
+ * Adds a listener that will be notified when any message or object
+ * is received from one of the clients.
+ */
+ public void addMessageListener( MessageListener<? super HostedConnection> listener );
+
+ /**
+ * Adds a listener that will be notified when messages of the specified
+ * types are received from one of the clients.
+ */
+ public void addMessageListener( MessageListener<? super HostedConnection> listener, Class... classes );
+
+ /**
+ * Removes a previously registered wildcard listener. This does
+ * not remove this listener from any type-specific registrations.
+ */
+ public void removeMessageListener( MessageListener<? super HostedConnection> listener );
+
+ /**
+ * Removes a previously registered type-specific listener from
+ * the specified types.
+ */
+ public void removeMessageListener( MessageListener<? super HostedConnection> listener, Class... classes );
+
+
+}
+
--- /dev/null
+/*
+ * 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.base;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.serializing.Serializer;
+
+/**
+ * Wraps a single Connector and forwards new messages
+ * to the supplied message dispatcher. This is used
+ * by DefaultClient to manage its connector objects.
+ * This is only responsible for message reading and provides
+ * no support for buffering writes.
+ *
+ * <p>This adapter assumes a simple protocol where two
+ * bytes define a (short) object size with the object data
+ * to follow. Note: this limits the size of serialized
+ * objects to 32676 bytes... even though, for example,
+ * datagram packets can hold twice that. :P</p>
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class ConnectorAdapter extends Thread
+{
+ private Connector connector;
+ private MessageListener dispatcher;
+ private AtomicBoolean go = new AtomicBoolean(true);
+
+ public ConnectorAdapter( Connector connector, MessageListener dispatcher )
+ {
+ super( String.valueOf(connector) );
+ this.connector = connector;
+ this.dispatcher = dispatcher;
+ setDaemon(true);
+ }
+
+ public void close()
+ {
+ go.set(false);
+
+ // Kill the connector
+ connector.close();
+ }
+
+ protected void createAndDispatch( ByteBuffer buffer )
+ {
+ try {
+ Object obj = Serializer.readClassAndObject( buffer );
+ Message m = (Message)obj;
+ dispatcher.messageReceived( null, m );
+ } catch( IOException e ) {
+ throw new RuntimeException( "Error deserializing object", e );
+ }
+ }
+
+ public void run()
+ {
+ ByteBuffer current = null;
+ int size = 0;
+
+ while( go.get() ) {
+ ByteBuffer buffer = connector.read();
+
+ // push the data from the buffer into as
+ // many messages as we can
+ while( buffer.remaining() > 0 ) {
+
+ if( current == null ) {
+ // We are not currently reading an object so
+ // grab the size.
+ // Note: this is somewhat limiting... int would
+ // be better.
+ size = buffer.getShort();
+ current = ByteBuffer.allocate(size);
+ }
+
+ if( current.remaining() <= buffer.remaining() ) {
+ // We have at least one complete object so
+ // copy what we can into current, create a message,
+ // and then continue pulling from buffer.
+
+ // Artificially set the limit so we don't overflow
+ int extra = buffer.remaining() - current.remaining();
+ buffer.limit( buffer.position() + current.remaining() );
+
+ // Now copy the data
+ current.put( buffer );
+ current.flip();
+
+ // Now set the limit back to a good value
+ buffer.limit( buffer.position() + extra );
+
+ createAndDispatch( current );
+
+ current = null;
+ } else {
+
+ // Not yet a complete object so just copy what we have
+ current.put( buffer );
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.base;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.network.*;
+import com.jme3.network.message.ClientRegistrationMessage; //hopefully temporary
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.serializing.Serializer;
+
+/**
+ * A default implementation of the Client interface that delegates
+ * its network connectivity to a kernel.Connector.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class DefaultClient implements Client
+{
+ static Logger log = Logger.getLogger(DefaultClient.class.getName());
+
+ private long id = -1;
+ private boolean isRunning = false;
+ private Connector reliable;
+ private Connector fast;
+ private MessageListenerRegistry<Client> messageListeners = new MessageListenerRegistry<Client>();
+ private List<ClientStateListener> stateListeners = new CopyOnWriteArrayList<ClientStateListener>();
+ private Redispatch dispatcher = new Redispatch();
+ private ConnectorAdapter reliableAdapter;
+ private ConnectorAdapter fastAdapter;
+
+ public DefaultClient( Connector reliable, Connector fast )
+ {
+ this.reliable = reliable;
+ this.fast = fast;
+ if( reliable != null ) {
+ reliableAdapter = new ConnectorAdapter(reliable, dispatcher);
+ }
+ if( fast != null ) {
+ fastAdapter = new ConnectorAdapter(fast, dispatcher);
+ }
+ }
+
+ protected void checkRunning()
+ {
+ if( !isRunning )
+ throw new IllegalStateException( "Client is not started." );
+ }
+
+ public void start()
+ {
+ if( isRunning )
+ throw new IllegalStateException( "Client is already started." );
+
+ // Start up the threads and stuff
+ if( reliableAdapter != null ) {
+ reliableAdapter.start();
+ }
+ if( fastAdapter != null ) {
+ fastAdapter.start();
+ }
+
+ // Send our connection message with a generated ID until
+ // we get one back from the server. We'll hash time in
+ // millis and time in nanos.
+ long tempId = System.currentTimeMillis() ^ System.nanoTime();
+
+ // Set it true here so we can send some messages.
+ isRunning = true;
+
+ ClientRegistrationMessage reg;
+ if( reliable != null ) {
+ reg = new ClientRegistrationMessage();
+ reg.setId(tempId);
+ reg.setReliable(true);
+ send(reg);
+ }
+ if( fast != null ) {
+ // We create two different ones to prepare for someday
+ // when there will probably be threaded sending.
+ reg = new ClientRegistrationMessage();
+ reg.setId(tempId);
+ reg.setReliable(false);
+ send(reg);
+ }
+ }
+
+ public boolean isConnected()
+ {
+ return id != -1; // for now
+ }
+
+ public long getId()
+ {
+ return id;
+ }
+
+ protected ByteBuffer messageToBuffer( Message message )
+ {
+ ByteBuffer buffer = ByteBuffer.allocate( 32767 + 2 );
+
+ try {
+ buffer.position( 2 );
+ Serializer.writeClassAndObject( buffer, message );
+ buffer.flip();
+ short dataLength = (short)(buffer.remaining() - 2);
+ buffer.putShort( dataLength );
+ buffer.position( 0 );
+
+ return buffer;
+ } catch( IOException e ) {
+ throw new RuntimeException( "Error serializing message", e );
+ }
+ }
+
+ public void send( Message message )
+ {
+ checkRunning();
+
+ // For now just send direclty. We allocate our
+ // own buffer each time because this method might
+ // be called from multiple threads. If writing
+ // is queued into its own thread then that could
+ // be shared.
+ ByteBuffer buffer = messageToBuffer(message);
+ if( message.isReliable() || fast == null ) {
+ if( reliable == null )
+ throw new RuntimeException( "No reliable connector configured" );
+ reliable.write(buffer);
+ } else {
+ fast.write(buffer);
+ }
+ }
+
+ public void close()
+ {
+ checkRunning();
+
+ // Send a close message
+
+ // Tell the thread it's ok to die
+ if( fastAdapter != null ) {
+ fastAdapter.close();
+ }
+ if( reliableAdapter != null ) {
+ reliableAdapter.close();
+ }
+
+ // Wait for the threads?
+
+ fireDisconnected();
+
+ isRunning = false;
+ }
+
+ public void addClientStateListener( ClientStateListener listener )
+ {
+ stateListeners.add( listener );
+ }
+
+ public void removeClientStateListener( ClientStateListener listener )
+ {
+ stateListeners.remove( listener );
+ }
+
+ public void addMessageListener( MessageListener<? super Client> listener )
+ {
+ messageListeners.addMessageListener( listener );
+ }
+
+ public void addMessageListener( MessageListener<? super Client> listener, Class... classes )
+ {
+ messageListeners.addMessageListener( listener, classes );
+ }
+
+ public void removeMessageListener( MessageListener<? super Client> listener )
+ {
+ messageListeners.removeMessageListener( listener );
+ }
+
+ public void removeMessageListener( MessageListener<? super Client> listener, Class... classes )
+ {
+ messageListeners.removeMessageListener( listener, classes );
+ }
+
+ protected void fireConnected()
+ {
+ for( ClientStateListener l : stateListeners ) {
+ l.clientConnected( this );
+ }
+ }
+
+ protected void fireDisconnected()
+ {
+ for( ClientStateListener l : stateListeners ) {
+ l.clientDisconnected( this );
+ }
+ }
+
+ protected void dispatch( Message m )
+ {
+ // Pull off the connection management messages we're
+ // interested in and then pass on the rest.
+ if( m instanceof ClientRegistrationMessage ) {
+ // Then we've gotten our real id
+ this.id = ((ClientRegistrationMessage)m).getId();
+ log.log( Level.INFO, "Connection established, id:{0}.", this.id );
+ fireConnected();
+ return;
+ }
+
+ messageListeners.messageReceived( this, m );
+ }
+
+ protected class Redispatch implements MessageListener
+ {
+ public void messageReceived( Object source, Message m )
+ {
+ dispatch( m );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.base;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.jme3.network.*;
+import com.jme3.network.kernel.*;
+import com.jme3.network.message.ClientRegistrationMessage; //hopefully temporary
+import com.jme3.network.serializing.Serializer;
+
+/**
+ * A default implementation of the Server interface that delegates
+ * its network connectivity to kernel.Kernel.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class DefaultServer implements Server
+{
+ private boolean isRunning = false;
+ private AtomicLong nextId = new AtomicLong(0);
+ private Kernel reliable;
+ private KernelAdapter reliableAdapter;
+ private Kernel fast;
+ private KernelAdapter fastAdapter;
+ private Redispatch dispatcher = new Redispatch();
+ private Map<Long,HostedConnection> connections = new ConcurrentHashMap<Long,HostedConnection>();
+ private Map<Endpoint,HostedConnection> endpointConnections
+ = new ConcurrentHashMap<Endpoint,HostedConnection>();
+
+ // Keeps track of clients for whom we've only received the UDP
+ // registration message
+ private Map<Long,Connection> connecting = new ConcurrentHashMap<Long,Connection>();
+
+ private MessageListenerRegistry<HostedConnection> messageListeners
+ = new MessageListenerRegistry<HostedConnection>();
+ private List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>();
+
+ public DefaultServer( Kernel reliable, Kernel fast )
+ {
+ this.reliable = reliable;
+ this.fast = fast;
+
+ if( reliable != null ) {
+ reliableAdapter = new KernelAdapter( this, reliable, dispatcher );
+ }
+ if( fast != null ) {
+ fastAdapter = new KernelAdapter( this, fast, dispatcher );
+ }
+ }
+
+ public void start()
+ {
+ if( isRunning )
+ throw new IllegalStateException( "Server is already started." );
+
+ // Initialize the kernels
+ if( reliable != null ) {
+ reliable.initialize();
+ }
+ if( fast != null ) {
+ fast.initialize();
+ }
+
+ // Start em up
+ if( reliableAdapter != null ) {
+ reliableAdapter.start();
+ }
+ if( fastAdapter != null ) {
+ fastAdapter.start();
+ }
+
+ isRunning = true;
+ }
+
+ public boolean isRunning()
+ {
+ return isRunning;
+ }
+
+ public void close()
+ {
+ if( !isRunning )
+ throw new IllegalStateException( "Server is not started." );
+
+ try {
+ // Kill the adpaters, they will kill the kernels
+ if( fastAdapter != null ) {
+ fastAdapter.close();
+ }
+ if( reliableAdapter != null ) {
+ reliableAdapter.close();
+
+ isRunning = false;
+ }
+ } catch( InterruptedException e ) {
+ throw new RuntimeException( "Interrupted while closing", e );
+ }
+ }
+
+ protected ByteBuffer messageToBuffer( Message message )
+ {
+ ByteBuffer buffer = ByteBuffer.allocate( 32767 + 2 );
+
+ try {
+ buffer.position( 2 );
+ Serializer.writeClassAndObject( buffer, message );
+ buffer.flip();
+ short dataLength = (short)(buffer.remaining() - 2);
+ buffer.putShort( dataLength );
+ buffer.position( 0 );
+
+ return buffer;
+ } catch( IOException e ) {
+ throw new RuntimeException( "Error serializing message", e );
+ }
+ }
+
+ public void broadcast( Message message )
+ {
+ broadcast( null, message );
+ }
+
+ public void broadcast( Object filter, Message message )
+ {
+ ByteBuffer buffer = messageToBuffer(message);
+
+ // Ignore the filter for the moment
+ if( message.isReliable() || fast == null ) {
+ if( reliable == null )
+ throw new RuntimeException( "No reliable kernel configured" );
+ reliable.broadcast( filter, buffer, true );
+ } else {
+ fast.broadcast( filter, buffer, false );
+ }
+ }
+
+ public HostedConnection getConnection( long id )
+ {
+ return connections.get(id);
+ }
+
+ public Collection<HostedConnection> getConnections()
+ {
+ return Collections.unmodifiableCollection((Collection<HostedConnection>)connections.values());
+ }
+
+ public void addConnectionListener( ConnectionListener listener )
+ {
+ connectionListeners.add(listener);
+ }
+
+ public void removeConnectionListener( ConnectionListener listener )
+ {
+ connectionListeners.remove(listener);
+ }
+
+ public void addMessageListener( MessageListener<? super HostedConnection> listener )
+ {
+ messageListeners.addMessageListener( listener );
+ }
+
+ public void addMessageListener( MessageListener<? super HostedConnection> listener, Class... classes )
+ {
+ messageListeners.addMessageListener( listener, classes );
+ }
+
+ public void removeMessageListener( MessageListener<? super HostedConnection> listener )
+ {
+ messageListeners.removeMessageListener( listener );
+ }
+
+ public void removeMessageListener( MessageListener<? super HostedConnection> listener, Class... classes )
+ {
+ messageListeners.removeMessageListener( listener, classes );
+ }
+
+ protected void dispatch( HostedConnection source, Message m )
+ {
+ messageListeners.messageReceived( source, m );
+ }
+
+ protected void fireConnectionAdded( HostedConnection conn )
+ {
+ for( ConnectionListener l : connectionListeners ) {
+ l.connectionAdded( this, conn );
+ }
+ }
+
+ protected void fireConnectionRemoved( HostedConnection conn )
+ {
+ for( ConnectionListener l : connectionListeners ) {
+ l.connectionRemoved( this, conn );
+ }
+ }
+
+ protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationMessage m )
+ {
+ Connection addedConnection = null;
+
+ // generally this will only be called by one thread but it's
+ // important enough I won't take chances
+ synchronized( this ) {
+ // Grab the random ID that the client created when creating
+ // its two registration messages
+ long tempId = m.getId();
+
+ // See if we already have one
+ Connection c = connecting.remove(tempId);
+ if( c == null ) {
+ c = new Connection();
+ }
+
+ // Fill in what we now know
+ if( ka == fastAdapter ) {
+ c.fast = p;
+
+ if( c.reliable == null ) {
+ // Tuck it away for later
+ connecting.put(tempId, c);
+ }
+
+ } else {
+ // It must be the reliable one
+ c.reliable = p;
+
+ if( c.fast == null && fastAdapter != null ) {
+ // Still waiting for the fast connection to
+ // register
+ connecting.put(tempId, c);
+ }
+ }
+
+ if( !connecting.containsKey(tempId) ) {
+
+ // Then we are fully connected
+ if( connections.put( c.getId(), c ) == null ) {
+
+ if( c.fast != null ) {
+ endpointConnections.put( c.fast, c );
+ }
+ endpointConnections.put( c.reliable, c );
+
+ addedConnection = c;
+ }
+ }
+ }
+
+ // Best to do this outside of the synch block to avoid
+ // over synchronizing which is the path to deadlocks
+ if( addedConnection != null ) {
+ // Nnow we can notify the listeners about the
+ // new connection.
+ fireConnectionAdded( addedConnection );
+
+ // Send the ID back to the client letting it know it's
+ // fully connected.
+ m = new ClientRegistrationMessage();
+ m.setId( addedConnection.getId() );
+ m.setReliable(true);
+ addedConnection.send(m);
+ }
+ }
+
+ protected HostedConnection getConnection( Endpoint endpoint )
+ {
+ return endpointConnections.get(endpoint);
+ }
+
+ protected void connectionClosed( Endpoint p )
+ {
+ // Try to find the endpoint in all ways that it might
+ // exist. Note: by this point the channel is closed
+ // already.
+
+ // Also note: this method will be called twice per
+ // HostedConnection if it has two endpoints.
+
+ Connection removed = null;
+ synchronized( this ) {
+ // Just in case the endpoint was still connecting
+ connecting.values().remove(p);
+
+ // And the regular management
+ removed = (Connection)endpointConnections.remove(p);
+ if( removed != null ) {
+ connections.remove( removed.getId() );
+ }
+ }
+
+ // Better not to fire events while we hold a lock
+ // so always do this outside the synch block.
+ if( removed != null ) {
+
+ // Make sure both endpoints are closed. Note: reliable
+ // should always already be closed through all paths that I
+ // can conceive... but it doesn't hurt to be sure.
+ if( removed.reliable != null && removed.reliable.isConnected() ) {
+ removed.reliable.close();
+ }
+ if( removed.fast != null && removed.fast.isConnected() ) {
+ removed.fast.close();
+ }
+
+ fireConnectionRemoved( removed );
+ }
+ }
+
+ protected class Connection implements HostedConnection
+ {
+ private long id;
+ private Endpoint reliable;
+ private Endpoint fast;
+
+ public Connection()
+ {
+ id = nextId.getAndIncrement();
+ }
+
+ public long getId()
+ {
+ return id;
+ }
+
+ public void send( Message message )
+ {
+ ByteBuffer buffer = messageToBuffer(message);
+ if( message.isReliable() || fast == null ) {
+ reliable.send( buffer );
+ } else {
+ fast.send( buffer );
+ }
+ }
+
+ public void close( String reason )
+ {
+ // Send a reason
+
+ // Just close the reliable endpoint
+ // fast will be cleaned up as a side-effect
+ if( reliable != null ) {
+ reliable.close();
+ }
+ }
+ }
+
+ protected class Redispatch implements MessageListener<HostedConnection>
+ {
+ public void messageReceived( HostedConnection source, Message m )
+ {
+ dispatch( source, m );
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.base;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.jme3.network.*;
+import com.jme3.network.kernel.Endpoint;
+import com.jme3.network.kernel.EndpointEvent;
+import com.jme3.network.kernel.Envelope;
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.message.ClientRegistrationMessage; //hopefully temporary
+import com.jme3.network.serializing.Serializer;
+
+/**
+ * Wraps a single Kernel and forwards new messages
+ * to the supplied message dispatcher and new endpoint
+ * events to the connection dispatcher. This is used
+ * by DefaultServer to manage its kernel objects.
+ *
+ * <p>This adapter assumes a simple protocol where two
+ * bytes define a (short) object size with the object data
+ * to follow. Note: this limits the size of serialized
+ * objects to 32676 bytes... even though, for example,
+ * datagram packets can hold twice that. :P</p>
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class KernelAdapter extends Thread
+{
+ private DefaultServer server; // this is unfortunate
+ private Kernel kernel;
+ private MessageListener messageDispatcher;
+ private AtomicBoolean go = new AtomicBoolean(true);
+
+ public KernelAdapter( DefaultServer server, Kernel kernel, MessageListener messageDispatcher )
+ {
+ super( String.valueOf(kernel) );
+ this.server = server;
+ this.kernel = kernel;
+ this.messageDispatcher = messageDispatcher;
+ setDaemon(true);
+ }
+
+ public void close() throws InterruptedException
+ {
+ go.set(false);
+
+ // Kill the kernel
+ kernel.terminate();
+ }
+
+ protected HostedConnection getConnection( Endpoint p )
+ {
+ return server.getConnection(p);
+ }
+
+ protected void connectionClosed( Endpoint p )
+ {
+ server.connectionClosed(p);
+ }
+
+ protected void createAndDispatch( Endpoint p, ByteBuffer buffer )
+ {
+ try {
+ Object obj = Serializer.readClassAndObject( buffer );
+ Message m = (Message)obj;
+
+ // Because this class is the only one with the information
+ // to do it... we need to pull of the registration message
+ // here.
+ if( m instanceof ClientRegistrationMessage ) {
+ server.registerClient( this, p, (ClientRegistrationMessage)m );
+ return;
+ }
+
+ HostedConnection source = getConnection(p);
+ messageDispatcher.messageReceived( source, m );
+ } catch( IOException e ) {
+ throw new RuntimeException( "Error deserializing object", e );
+ }
+ }
+
+ protected void createAndDispatch( Envelope env )
+ {
+ byte[] data = env.getData();
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+
+ ByteBuffer current = null;
+ int size = 0;
+
+ // push the data from the buffer into as
+ // many messages as we can
+ while( buffer.remaining() > 0 ) {
+
+ if( current == null ) {
+ // We are not currently reading an object so
+ // grab the size.
+ // Note: this is somewhat limiting... int would
+ // be better.
+ size = buffer.getShort();
+ current = ByteBuffer.allocate(size);
+ }
+
+ if( current.remaining() <= buffer.remaining() ) {
+ // We have at least one complete object so
+ // copy what we can into current, create a message,
+ // and then continue pulling from buffer.
+
+ // Artificially set the limit so we don't overflow
+ int extra = buffer.remaining() - current.remaining();
+ buffer.limit( buffer.position() + current.remaining() );
+
+ // Now copy the data
+ current.put( buffer );
+ current.flip();
+
+ // Now set the limit back to a good value
+ buffer.limit( buffer.position() + extra );
+
+ createAndDispatch( env.getSource(), current );
+
+ current = null;
+ } else {
+
+ // Not yet a complete object so just copy what we have
+ current.put( buffer );
+ }
+ }
+
+ }
+
+ protected void createAndDispatch( EndpointEvent event )
+ {
+ // Only need to tell the server about disconnects
+ if( event.getType() == EndpointEvent.Type.REMOVE ) {
+ connectionClosed( event.getEndpoint() );
+ }
+ }
+
+ protected void flushEvents()
+ {
+ EndpointEvent event;
+ while( (event = kernel.nextEvent()) != null )
+ {
+ createAndDispatch( event );
+ }
+ }
+
+ public void run()
+ {
+ while( go.get() ) {
+
+ try {
+ // Check for pending events
+ flushEvents();
+
+ // Grab the next envelope
+ Envelope e = kernel.read();
+
+ // Check for pending events that might have
+ // come in while we were blocking. This is usually
+ // when the connection add events come through
+ flushEvents();
+
+ createAndDispatch( e );
+ } catch( InterruptedException ex ) {
+ if( !go.get() )
+ return;
+ throw new RuntimeException( "Unexpected interruption", ex );
+ }
+ }
+ }
+
+}
+
+
--- /dev/null
+/*
+ * 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.base;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+
+/**
+ * Keeps track of message listeners registered to specific
+ * types or to any type.
+ *
+ * @version $Revision$
+ * @author Paul Speed
+ */
+public class MessageListenerRegistry<S> implements MessageListener<S>
+{
+ private List<MessageListener<? super S>> listeners = new CopyOnWriteArrayList<MessageListener<? super S>>();
+ private Map<Class,List<MessageListener<? super S>>> typeListeners
+ = new ConcurrentHashMap<Class,List<MessageListener<? super S>>>();
+
+ public MessageListenerRegistry()
+ {
+ }
+
+ public void messageReceived( S source, Message m )
+ {
+ for( MessageListener<? super S> l : listeners ) {
+ l.messageReceived( source, m );
+ }
+
+ for( MessageListener<? super S> l : getListeners(m.getClass(),false) ) {
+ l.messageReceived( source, m );
+ }
+ }
+
+ protected List<MessageListener<? super S>> getListeners( Class c, boolean create )
+ {
+ List<MessageListener<? super S>> result = typeListeners.get(c);
+ if( result == null && create ) {
+ result = new CopyOnWriteArrayList<MessageListener<? super S>>();
+ typeListeners.put( c, result );
+ }
+
+ if( result == null ) {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ public void addMessageListener( MessageListener<? super S> listener )
+ {
+ listeners.add(listener);
+ }
+
+ public void removeMessageListener( MessageListener<? super S> listener )
+ {
+ listeners.remove(listener);
+ }
+
+ public void addMessageListener( MessageListener<? super S> listener, Class... classes )
+ {
+ for( Class c : classes ) {
+ getListeners(c, true).add(listener);
+ }
+ }
+
+ public void removeMessageListener( MessageListener<? super S> listener, Class... classes )
+ {
+ for( Class c : classes ) {
+ getListeners(c, false).remove(listener);
+ }
+ }
+}
* @author Lars Wesselius
*/
@Serializable()
-public class Message {
+public class Message implements com.jme3.network.Message {
// The connector this message is meant for.
private transient Client connector;
private transient Connection connection;