2 <h1><a>SpiderMonkey: Multi-Player Networking</a></h1>
7 This document introduces you to the SpiderMonkey networking <acronym title="Application Programming Interface">API</acronym>. You use this <acronym title="Application Programming Interface">API</acronym> when you develop games where several players compete with one another in real time. A multi-player game is made up of several clients connecting to a server:
11 <li><div> The central server (one headless SimpleApplication) coordinates the game in the background.</div>
13 <li><div> Each player runs a game client (a standard SimpleApplication) and connects to the central server.</div>
19 Each Client keeps the Server informed about its player's moves and actions. The Server centrally maintains the game state and broadcasts the state info back to all connected clients. This network synchronization allows all clients to share the same game world. Each client then displays the game state to one player from this player's perspective.
23 <!-- EDIT1 SECTION "SpiderMonkey: Multi-Player Networking" [1-841] -->
24 <h2><a>SpiderMonkey API Overview</a></h2>
29 The SpiderMonkey <acronym title="Application Programming Interface">API</acronym> is a set of interfaces and helper classes in the 'com.jme3.network' package. For most users, this package and the 'message' package is all they need to worry about. (The 'base' and 'kernel' packages only come into play when implementing custom network transports or alternate client/server protocols, which is now possible).
33 The SpiderMonkey <acronym title="Application Programming Interface">API</acronym> assists you in creating a Server, Clients, and Messages. Once a Server instance is created and started, the Server accepts remote connections from Clients, and you can send and receive Messages. Client objects represent the client-side of the client-server connection. Within the Server, these Client objects are referred to as HostedConnections. HostedConnections can hold application-defined client-specific session attributes that the server-side listeners and services can use to track player information, etc.
38 <th> Seen from the Client </th><th> </th><th> Seen from the Server </th>
41 <td> com.jme3.network.Client </td><td> == </td><td> com.jme3.network.HostedConnection </td>
44 <!-- EDIT3 TABLE [1767-1885] -->
47 You can register several types of listeners to be notified of changes.
50 <li><div> MessageListeners on both the Client and the Server are notified when new messages arrive. You can use MessageListeners to be notified about only specific types of messages.</div>
52 <li><div> ClientStateListeners inform the Client of changes in its connection state, e.g. when the client gets kicked from the server.</div>
54 <li><div> ConnectionListeners inform the Server about HostedConnection arrivals and removals, e.g. if a client joins or quits.</div>
59 <!-- EDIT2 SECTION "SpiderMonkey API Overview" [842-2386] -->
60 <h2><a>Client and Server</a></h2>
64 <!-- EDIT4 SECTION "Client and Server" [2387-2417] -->
65 <h3><a>Creating a Server</a></h3>
70 The game server is a "headless" com.jme3.app.SimpleApplication:
72 <pre>public class ServerMain extends SimpleApplication {
73 public static void main(String[] args) {
74 ServerMain app = new ServerMain();
75 app.start(JmeContext.Type.Headless); // headless type for servers!
80 <p><div>A <code>Headless</code> SimpleApplication executes the simpleInitApp() method and runs the update loop normally. But the application does not open a window, and it does not listen to user input. This is the typical behavior for a server application.
85 Create a com.jme3.network.Server in the <code>simpleInitApp()</code> method and specify a communication port, for example 6143.
87 <pre> public void simpleInitApp() {
89 Server myServer = Network.createServer(6143);
90 myServer.start();
95 When you run this app on a host, the server is ready to accept clients. Let's create a client next.
99 <!-- EDIT5 SECTION "Creating a Server" [2418-3368] -->
100 <h3><a>Creating a Client</a></h3>
105 A game client is a standard com.jme3.app.SimpleApplication.
107 <pre>public class ClientMain extends SimpleApplication {
108 public static void main(String[] args) {
109 ClientMain app = new ClientMain();
110 app.start(JmeContext.Type.Display); // standard display type
115 <p><div>A standard SimpleApplication in <code>Display</code> mode executes the simpleInitApp() method, runs the update loop, opens a window for the rendered video output, and listens to user input. This is the typical behavior for a client application.
121 Create a com.jme3.network.Client in the <code>simpleInitApp()</code> method and specify the servers IP address, and the same communication port as for the server, here 6143.
123 <pre>public void simpleInitApp() {
125 Client myClient = Network.connectToServer("localhost", 6143);
126 myClient.start();
130 The server address can be in the format "localhost" or "127.0.0.1" (for local testing), or an IP address of a remote host in the format ???123.456.78.9???. In this example, we assume the server is running on the localhost.
134 When you run this client, it connects to the server.
138 <!-- EDIT6 SECTION "Creating a Client" [3369-4534] -->
139 <h3><a>Getting Info About a Client</a></h3>
144 The server refers to a connected client as com.jme3.network.HostedConnection objects. The server can get info about clients as follows:
149 <th>Accessor</th><th>Purpose</th>
152 <td>myServer.getConnections()</td><td>Server gets a collection of all connected HostedConnection objects (all connected clients).</td>
155 <td>myServer.getConnections().size()</td><td>Server gets the number of all connected HostedConnection objects (number of clients).</td>
158 <td>myServer.getConnection(0)</td><td>Server gets the first (0), second (1), etc, connected HostedConnection object (one client).</td>
161 <!-- EDIT8 TABLE [4711-5090] -->
164 Your game can define its own game data based on whatever criteria you want, typically these include player ID and state. If the server needs to look up player/client-specific information, you can store this information directly on the HostedConnection object. The following examples read and write a custom Java object <code>MyState</code> in the HostedConnection object <code>conn</code>:
169 <th>Accessor</th><th>Purpose</th>
172 <td> conn.setAttribute("MyState", new MyState()); </td><td> Server can change an attribute of the HostedConnection. </td>
175 <td> MyState state = conn.getAttribute("MyState")</td><td> Server can read an attribute of the HostedConnection. </td>
178 <!-- EDIT9 TABLE [5465-5694] -->
180 <!-- EDIT7 SECTION "Getting Info About a Client" [4535-5695] -->
181 <h2><a>Messaging</a></h2>
185 <!-- EDIT10 SECTION "Messaging" [5696-5718] -->
186 <h3><a>Creating Message Types</a></h3>
191 Each message represents data that you want to transmit between client and server. Common message examples include transformation updates or game actions. For each message type, create a message class that extends com.jme3.network.AbstractMessage. Use the @Serializable annotation from com.jme3.network.serializing.Serializable and create an empty default constructor. Custom constructors, fields, and methods are up to you and depend on the message data that you want to transmit.
194 public class HelloMessage extends AbstractMessage {
195 private String hello; // custom message data
196 public HelloMessage() {} // empty constructor
197 public HelloMessage(String s) { hello = s; } // custom constructor
201 You must register each message type to the com.jme3.network.serializing.Serializer, in both server and client!
203 <pre>Serializer.registerClass(HelloMessage.class);</pre>
206 <!-- EDIT11 SECTION "Creating Message Types" [5719-6671] -->
207 <h3><a>Responding to Messages</a></h3>
212 After a Message was received, a Listener responds to it. The listener can access fields of the message, and send messages back, start new threads, etc. There are two listeners, one on the server, one on the client. For each message type, you implement the responses in either Listeners??? <code>messageReceived()</code> method.
217 <h4><a>ClientListener.java</a></h4>
222 Create one ClientListener.java and make it extend <code>com.jme3.network.MessageListener</code>.
225 <pre>public class ClientListener implements MessageListener<Client> {
226 public void messageReceived(Client source, Message message) {
227 if (message instanceof HelloMessage) {
228 // do something with the message
229 HelloMessage helloMessage = (HelloMessage) message;
230 System.out.println("Client #"+source.getId()+" received: '"+helloMessage.getSomething()+"'");
235 For each message type, register a client listener to the client.
237 <pre>myClient.addMessageListener(new ClientListener(), HelloMessage.class);</pre>
241 <h4><a>ServerListener.java</a></h4>
246 Create one ServerListener.java and make it extend <code>com.jme3.network.MessageListener</code>.
248 <pre>public class ServerListener implements MessageListener<HostedConnection> {
249 public void messageReceived(HostedConnection source, Message message) {
250 if (message instanceof HelloMessage) {
251 // do something with the message
252 HelloMessage helloMessage = (HelloMessage) message;
253 System.out.println("Server received '" +helloMessage.getSomething() +"' from client #"+source.getId() );
258 For each message type, register a server listener to the server:
260 <pre>myServer.addMessageListener(new ServerListener(), HelloMessage.class);</pre>
263 <!-- EDIT12 SECTION "Responding to Messages" [6672-8416] -->
264 <h3><a>Creating and Sending Messages</a></h3>
269 Let's create a new message of type HelloMessage:
271 <pre>Message message = new HelloMessage("Hello World!");</pre>
274 Now the client can send this message to the server:
276 <pre>myClient.send(message);</pre>
279 Or the server can broadcast this message to all HostedConnection (clients):
281 <pre>Message message = new HelloMessage("Welcome!");
282 myServer.broadcast(message);</pre>
285 Or the server can send the message to a specific subset of clients (e.g. to HostedConnection conn1, conn2, and conn3):
287 <pre>myServer.broadcast( Filters.in( conn1, conn2, conn3 ), message );</pre>
290 Or the server can send the message to all but a few selected clients (e.g. to all HostedConnections but conn4):
293 <pre>myServer.broadcast( Filters.notEqualTo( conn4 ), message );</pre>
296 The last two broadcasting methods use com.jme3.network.Filters to select a subset of recipients. If you know the exact list of recipients, always send the messages directly to them using the Filters; avoid flooding the network with unnecessary broadcasts to all.
300 <!-- EDIT13 SECTION "Creating and Sending Messages" [8417-9506] -->
301 <h2><a>Identification and Rejection</a></h2>
306 The ID of the Client and HostedConnection are the same at both ends of a connection. The ID is given out authoritatively by the Server.
308 <pre>... myClient.getId() ...</pre>
311 A server has a game version and game name property. Each client expects to communicate with a server with a certain game name and version. Test first whether the game name matches, and then whether game version matches, before sending any messages! If they do not match, you should refuse to connect, because unmatched clients and servers will likely miscommunicate.
315 <p><div>Typically, your networked game defines its own attributes (such as player ID) based on whatever criteria you want. If you want to look up player/client-specific information beyond the game version, you can set this information directly on the Client/HostedConnection object (see Getting Info About a Client).
320 <!-- EDIT14 SECTION "Identification and Rejection" [9507-10424] -->
321 <h2><a>Closing Clients and Server Cleanly</a></h2>
325 <!-- EDIT15 SECTION "Closing Clients and Server Cleanly" [10425-10471] -->
326 <h3><a>Closing a Client</a></h3>
331 You must override the client's destroy() method to close the connection cleanly when the player quits the client:
334 public void destroy() {
336 myClient.close();
337 super.destroy();
341 <!-- EDIT16 SECTION "Closing a Client" [10472-10747] -->
342 <h3><a>Closing a Server</a></h3>
347 You must override the server's destroy() method to close the connection when the server quits:
350 public void destroy() {
352 myServer.close();
353 super.destroy();
357 <!-- EDIT17 SECTION "Closing a Server" [10748-11004] -->
358 <h3><a>Kicking a Client</a></h3>
363 The server can kick a HostedConnection to make it disconnect. You should provide a String with further info (an explanation to the user what happened, e.g. "Shutting down for maintenance") for the server to send along. This info message can be used (displayed to the user) by a ClientStateListener. (See below)
365 <pre>conn.close("We kick cheaters.");</pre>
368 <!-- EDIT18 SECTION "Kicking a Client" [11005-11395] -->
369 <h2><a>Listening to Connection Notification</a></h2>
374 The server and clients are notified about connection changes.
378 <!-- EDIT19 SECTION "Listening to Connection Notification" [11396-11507] -->
379 <h3><a>ClientStateListener</a></h3>
384 The com.jme3.network.ClientStateListener notifies the Client when the Client has fully connected to the server (including any internal handshaking), and when the Client is kicked (disconnected) from the server.
389 <th> ClientStateListener interface method </th><th> Purpose </th>
392 <td> public void clientConnected(Client c){} </td><td> Implement here what happens as soon as this client has fully connected to the server. </td>
395 <td> public void clientDisconnected(Client c, DisconnectInfo info){} </td><td> Implement here what happens after the server kicks this client. For example, display the DisconnectInfo to the user. </td>
398 <!-- EDIT21 TABLE [11750-12119] -->
401 First implement the ClientStateListener interface in the Client class. Then register it to myClient in MyGameClient's simpleInitApp() method:
403 <pre>myClient.addClientStateListener(this);</pre>
406 <!-- EDIT20 SECTION "ClientStateListener" [11508-12321] -->
407 <h3><a>ConnectionListener</a></h3>
412 The com.jme3.network.ConnectionListener notifies the Server whenever new HostedConnections (clients) come and go. The listener notifies the server after the Client connection is fully established (including any internal handshaking).
417 <th> ConnectionListener interface method </th><th> Purpose </th>
420 <td> public void connectionAdded(Server s, HostedConnection c){} </td><td> Implemenent here what happens after a new HostedConnection has joined the Server. </td>
423 <td> public void connectionRemoved(Server s, HostedConnection c){} </td><td> Implement here what happens after a HostedConnection has left. E.g. a player has quit the game and the server removes his character. </td>
426 <!-- EDIT23 TABLE [12587-12985] -->
429 First implement the ConnectionListener interface in the Server class. Then register it to myServer in MyGameServer's simpleInitApp() method.
432 <pre>myServer.addConnectionListener(this);</pre>
435 <!-- EDIT22 SECTION "ConnectionListener" [12322-13185] -->
436 <h2><a>UDP versus TCP</a></h2>
441 SpiderMonkey supports both UDP (unreliable, fast) and TCP (reliable, slow) transport of messages.
443 <pre>message1.setReliable(true); // TCP
444 message2.setReliable(false); // UDP</pre>
446 <li><div> Choose reliable and slow transport for messages, if you want to make certain the message is delivered (resent) when lost, and if the order of a series of messages is relevant. E.g. game actions such as "1. wield weapon, 2. attack, 3. dodge".</div>
448 <li><div> Choose unreliable and fast transport for messages if the next message makes any previously delayed or lost message obsolete and synchronizes the state again. E.g. a series of new locations while walking.</div>
453 <!-- EDIT24 SECTION "UDP versus TCP" [13186-13856] -->
454 <h2><a>Important: Use Multi-Threading</a></h2>
459 <p><div><strong>You cannot modify the scenegraph directly from the network thread.</strong> A common example for such a modification is when you synchronize the player's position in the scene. You have to use Java Multithreading.
464 Multithreading means that you create a Callable. A Callable is a Java class representing any (possibly time-intensive) self-contained task that has an impact on the scene graph (such as positioning the player). You enqueue the Callable in the Executor of the client's OpenGL thread. The Callable ensures to executes the modification in sync with the update loop.
466 <pre>app.enqueue(callable);</pre>
469 Learn more about using <a href="/com/jme3/gde/docs/jme3/advanced/multithreading.html">multithreading</a> in jME3 here.
473 For general advice, see the articles <object classid="java:org.netbeans.modules.javahelp.BrowserDisplayer"><param name="content" value="https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking"><param name="text" value="<html><u>MultiPlayer Networking</u></html>"><param name="textColor" value="blue"></object> and <object classid="java:org.netbeans.modules.javahelp.BrowserDisplayer"><param name="content" value="https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization"><param name="text" value="<html><u>Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization</u></html>"><param name="textColor" value="blue"></object> by the Valve Developer Community.
477 <!-- EDIT25 SECTION "Important: Use Multi-Threading" [13857-15003] -->
478 <h2><a>Troubleshooting</a></h2>
483 If you have set up a server in your home network, and the game clients cannot reach the server from the outside, it's time to learn about <object classid="java:org.netbeans.modules.javahelp.BrowserDisplayer"><param name="content" value="http://portforward.com/"><param name="text" value="<html><u>port forwarding</u></html>"><param name="textColor" value="blue"></object>.
486 <a href="/wiki/doku.php/tag:documentation?do=showtag&tag=tag%3Adocumentation">documentation</a>,
487 <a href="/wiki/doku.php/tag:network?do=showtag&tag=tag%3Anetwork">network</a>,
488 <a href="/wiki/doku.php/tag:spidermonkey?do=showtag&tag=tag%3Aspidermonkey">spidermonkey</a>
492 <!-- EDIT26 SECTION "Troubleshooting" [15004-] -->
493 <p><em><a href="http://jmonkeyengine.org/wiki/doku.php/jme3:advanced:networking?do=export_xhtmlbody">view online version</a></em></p>