Not as wordy as my usual posts but hopefully this is clear enough and as I’ve said before, I’m relatively new to Java so there may be better ways of doing things. Anyway, here’s my client network communications code.
The bits we’re particularly interested in here interested in are the java.util.concurrent classes (look them up), my ConnectionFlag class and addClientStateListener.
The client state listener is called when the connection state changes. This lock used by the listener tells the class when everything using ConnectionFlag connected.
A call to start() in turn calls connected.waitForConnected();
which doesn’t return until everything is ready. Obviously, the server doesn’t need any of this guff.
[edit: Comms is my abstract class underlying my ServerComms and ClientComms classes]
import com.jme3.network.Client;
import com.jme3.network.ClientStateListener;
import com.jme3.network.Network;
import com.jme3.network.Message;
import java.io.IOException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Network client connection using SpiderMonkey
*
* Incoming messages are placed in order into a message queue, and can be read
* in order from there All classes my be @serializable and passed to the
* constructor.
*
*/
public class ClientComms extends Comms {
private static final Logger logger = Logger.getLogger(ClientComms.class.getName());
Client client;
int port = -1;
String ipAddress = null;
String name = null;
ConnectionFlag connected = new ConnectionFlag();
/**
*
*/
public ClientComms() {
initialiseComms();
}
/**
* open the network connection, ready to receive messges
*
* @param name string identifier of connection; only used for reporting at
* startup, non functional
* @param ipAddress ip address of the server to connect to
* @param port the port number to open, listening for incoming messages
* @param classes array of classes the server will send as messages
*
* @return true on success, false on failure
*/
public boolean start(String name, String ipAddress, int port, Class <?>[] classes) {
boolean restart = this.port != -1;
this.classes = classes;
try {
client = Network.connectToServer(ipAddress, port);
} catch (IOException e) {
logger.log(Level.SEVERE, "Failed to start Client for {0} on Port {1}", new Object[]{name, Integer.toString(port)});
return (false);
}
if (!restart)
client.addClientStateListener(new GameClientStateListener(connected));
// initialiseSerialization(classes);
client.start();
if (!restart)
addMessageListeners(classes);
this.port = port;
this.name = name;
try {
connected.waitForConnected();
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, null, ex);
}
logger.log(Level.INFO, "Started Client for {0} on Port {1}", new Object[]{name, Integer.toString(port)});
return (true);
}
/**
* disconnect from the server; any unread messages still in the queue remain
* available for reading
*
* @return true on success, false on failure
*/
public boolean stop() {
if (!connected.getConnected()) {
logger.log(Level.SEVERE, "Network CLIENT already stopped!");
return (false);
}
client.close();
logger.log(Level.SEVERE, "Stopped Client");
return (true);
}
/**
* reconnect to the server, using the original settings
*
* @return true on success, false on failure
*/
public boolean restart() {
if (port == -1) {
logger.log(Level.SEVERE, "Network CLIENT has not previously connected!");
return (false);
}
if (connected.getConnected()) {
logger.log(Level.SEVERE, "Network CLIENT is already connected!");
return (false);
}
return (start(name, ipAddress, port, classes));
}
/**
* send a message to the server must already be connected
*
* @param message
* @return true on success, false on failure
*/
public boolean send(Message message) {
if (message == null) {
logger.log(Level.SEVERE, "Client sent NULL message:\n");
return (false);
}
if (message instanceof StringMessage) {
logger.log(Level.SEVERE, "Client attempting to send StringMessage:\n {0}", ((StringMessage) message).getString());
}
client.send(message);
logger.log(Level.INFO, "Client sent message:\n {0}", message);
return (true);
}
/**
* Read the next message from the message queue
*
* @return the IdedMessage from queue comprising the message and the
* connection id of the sender
*/
public IdedMessage read() {
return (messageQueue.read());
}
private void addMessageListeners(Class<?>[] classes) {
for (Class clazz: classes) {
addMessageListener(clazz);
}
}
private void addMessageListener(Class<?> clazz) {
client.addMessageListener(new ClientListener(messageQueue), clazz);
}
private class GameClientStateListener implements ClientStateListener {
ConnectionFlag connectedFlag;
GameClientStateListener(ConnectionFlag connectedFlag) {
this.connectedFlag = connectedFlag;
}
@Override
public void clientConnected(Client c) {
try {
connectedFlag.connect();
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, null, ex);
}
System.out.println("clientConnected(" + c + ")");
}
@Override
public void clientDisconnected(Client c, DisconnectInfo info) {
try {
connectedFlag.disconnect();
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, null, ex);
}
System.out.println("clientDisconnected(" + c + "):" + info);
}
}
private class ConnectionFlag {
final Lock lock = new ReentrantLock();
final Condition connection = lock.newCondition();
final Condition disconnection = lock.newCondition();
private boolean connected = false;
public void waitForConnected() throws InterruptedException {
lock.lock();
if (!getConnected())
connection.await();
lock.unlock();
}
public void connect() throws InterruptedException {
lock.lock();
connection.signal();
connected = true;
lock.unlock();
}
public void disconnect() throws InterruptedException {
lock.lock();
connected = false;
disconnection.signal();
lock.unlock();
}
public synchronized boolean getConnected() {
return(connected);
}
}
}