diff --git a/engine/src/networking/com/jme3/network/Client.java b/engine/src/networking/com/jme3/network/Client.java new file mode 100644 index 000000000..7e362b47e --- /dev/null +++ b/engine/src/networking/com/jme3/network/Client.java @@ -0,0 +1,111 @@ +/* + * 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 listener ); + + /** + * Adds a listener that will be notified when messages of the specified + * types are received. + */ + public void addMessageListener( MessageListener listener, Class... classes ); + + /** + * Removes a previously registered wildcard listener. This does + * not remove this listener from any type-specific registrations. + */ + public void removeMessageListener( MessageListener listener ); + + /** + * Removes a previously registered type-specific listener from + * the specified types. + */ + public void removeMessageListener( MessageListener listener, Class... classes ); + +} + + diff --git a/engine/src/networking/com/jme3/network/ClientStateListener.java b/engine/src/networking/com/jme3/network/ClientStateListener.java new file mode 100644 index 000000000..271fdfba7 --- /dev/null +++ b/engine/src/networking/com/jme3/network/ClientStateListener.java @@ -0,0 +1,56 @@ +/* + * 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 ); +} diff --git a/engine/src/networking/com/jme3/network/ConnectionListener.java b/engine/src/networking/com/jme3/network/ConnectionListener.java new file mode 100644 index 000000000..b7d91ff8f --- /dev/null +++ b/engine/src/networking/com/jme3/network/ConnectionListener.java @@ -0,0 +1,56 @@ +/* + * 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 ); +} diff --git a/engine/src/networking/com/jme3/network/HostedConnection.java b/engine/src/networking/com/jme3/network/HostedConnection.java new file mode 100644 index 000000000..e0daec48a --- /dev/null +++ b/engine/src/networking/com/jme3/network/HostedConnection.java @@ -0,0 +1,55 @@ +/* + * 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 ); +} diff --git a/engine/src/networking/com/jme3/network/Message.java b/engine/src/networking/com/jme3/network/Message.java new file mode 100644 index 000000000..9f8e4d080 --- /dev/null +++ b/engine/src/networking/com/jme3/network/Message.java @@ -0,0 +1,47 @@ +/* + * 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(); +} diff --git a/engine/src/networking/com/jme3/network/MessageConnection.java b/engine/src/networking/com/jme3/network/MessageConnection.java new file mode 100644 index 000000000..712d82108 --- /dev/null +++ b/engine/src/networking/com/jme3/network/MessageConnection.java @@ -0,0 +1,50 @@ +/* + * 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 ); +} + diff --git a/engine/src/networking/com/jme3/network/MessageListener.java b/engine/src/networking/com/jme3/network/MessageListener.java new file mode 100644 index 000000000..4d3f26a7e --- /dev/null +++ b/engine/src/networking/com/jme3/network/MessageListener.java @@ -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; + + +/** + * Listener notified about new messages + * + * @version $Revision$ + * @author Paul Speed + */ +public interface MessageListener +{ + public void messageReceived( S source, Message m ); +} diff --git a/engine/src/networking/com/jme3/network/Network.java b/engine/src/networking/com/jme3/network/Network.java new file mode 100644 index 000000000..7e9dc0254 --- /dev/null +++ b/engine/src/networking/com/jme3/network/Network.java @@ -0,0 +1,92 @@ +/* + * 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 ); + } +} diff --git a/engine/src/networking/com/jme3/network/Server.java b/engine/src/networking/com/jme3/network/Server.java new file mode 100644 index 000000000..9f36e9a33 --- /dev/null +++ b/engine/src/networking/com/jme3/network/Server.java @@ -0,0 +1,121 @@ +/* + * 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 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 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 listener, Class... classes ); + + /** + * Removes a previously registered wildcard listener. This does + * not remove this listener from any type-specific registrations. + */ + public void removeMessageListener( MessageListener listener ); + + /** + * Removes a previously registered type-specific listener from + * the specified types. + */ + public void removeMessageListener( MessageListener listener, Class... classes ); + + +} + diff --git a/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java b/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java new file mode 100644 index 000000000..f3499f9b3 --- /dev/null +++ b/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java @@ -0,0 +1,142 @@ +/* + * 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. + * + *

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

+ * + * @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 ); + } + } + } + } + +} diff --git a/engine/src/networking/com/jme3/network/base/DefaultClient.java b/engine/src/networking/com/jme3/network/base/DefaultClient.java new file mode 100644 index 000000000..3a0dca4f9 --- /dev/null +++ b/engine/src/networking/com/jme3/network/base/DefaultClient.java @@ -0,0 +1,258 @@ +/* + * 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 messageListeners = new MessageListenerRegistry(); + private List stateListeners = new CopyOnWriteArrayList(); + 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 listener ) + { + messageListeners.addMessageListener( listener ); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.addMessageListener( listener, classes ); + } + + public void removeMessageListener( MessageListener listener ) + { + messageListeners.removeMessageListener( listener ); + } + + public void removeMessageListener( MessageListener 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 ); + } + } +} diff --git a/engine/src/networking/com/jme3/network/base/DefaultServer.java b/engine/src/networking/com/jme3/network/base/DefaultServer.java new file mode 100644 index 000000000..78c89b9e8 --- /dev/null +++ b/engine/src/networking/com/jme3/network/base/DefaultServer.java @@ -0,0 +1,390 @@ +/* + * 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 connections = new ConcurrentHashMap(); + private Map endpointConnections + = new ConcurrentHashMap(); + + // Keeps track of clients for whom we've only received the UDP + // registration message + private Map connecting = new ConcurrentHashMap(); + + private MessageListenerRegistry messageListeners + = new MessageListenerRegistry(); + private List connectionListeners = new CopyOnWriteArrayList(); + + 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 getConnections() + { + return Collections.unmodifiableCollection((Collection)connections.values()); + } + + public void addConnectionListener( ConnectionListener listener ) + { + connectionListeners.add(listener); + } + + public void removeConnectionListener( ConnectionListener listener ) + { + connectionListeners.remove(listener); + } + + public void addMessageListener( MessageListener listener ) + { + messageListeners.addMessageListener( listener ); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.addMessageListener( listener, classes ); + } + + public void removeMessageListener( MessageListener listener ) + { + messageListeners.removeMessageListener( listener ); + } + + public void removeMessageListener( MessageListener 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 + { + public void messageReceived( HostedConnection source, Message m ) + { + dispatch( source, m ); + } + } + +} diff --git a/engine/src/networking/com/jme3/network/base/KernelAdapter.java b/engine/src/networking/com/jme3/network/base/KernelAdapter.java new file mode 100644 index 000000000..6c861c877 --- /dev/null +++ b/engine/src/networking/com/jme3/network/base/KernelAdapter.java @@ -0,0 +1,210 @@ +/* + * 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. + * + *

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

+ * + * @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 ); + } + } + } + +} + + diff --git a/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java b/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java new file mode 100644 index 000000000..fae0cf31b --- /dev/null +++ b/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java @@ -0,0 +1,106 @@ +/* + * 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 implements MessageListener +{ + private List> listeners = new CopyOnWriteArrayList>(); + private Map>> typeListeners + = new ConcurrentHashMap>>(); + + public MessageListenerRegistry() + { + } + + public void messageReceived( S source, Message m ) + { + for( MessageListener l : listeners ) { + l.messageReceived( source, m ); + } + + for( MessageListener l : getListeners(m.getClass(),false) ) { + l.messageReceived( source, m ); + } + } + + protected List> getListeners( Class c, boolean create ) + { + List> result = typeListeners.get(c); + if( result == null && create ) { + result = new CopyOnWriteArrayList>(); + typeListeners.put( c, result ); + } + + if( result == null ) { + result = Collections.emptyList(); + } + return result; + } + + public void addMessageListener( MessageListener listener ) + { + listeners.add(listener); + } + + public void removeMessageListener( MessageListener listener ) + { + listeners.remove(listener); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + for( Class c : classes ) { + getListeners(c, true).add(listener); + } + } + + public void removeMessageListener( MessageListener listener, Class... classes ) + { + for( Class c : classes ) { + getListeners(c, false).remove(listener); + } + } +} diff --git a/engine/src/networking/com/jme3/network/message/Message.java b/engine/src/networking/com/jme3/network/message/Message.java index 03acc5dfc..e7dbf68df 100644 --- a/engine/src/networking/com/jme3/network/message/Message.java +++ b/engine/src/networking/com/jme3/network/message/Message.java @@ -44,7 +44,7 @@ import com.jme3.network.serializing.Serializable; * @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;