Firstly, my code is just a demo of my multiplayer game (2 or more players can play simultaneously) to demonstrate my problem without any extra things. I have successfully implemented peer-to-peer (P2P) communication in my game. Later, I decided to add support for client/server communication (ie a central server which is also a player) in my game. It should be much easier than P2P. But strange! Unfortunately I'm facing the problem which I have no clue to solve it! Now here is the problem:
Suppose, I have 1 server and some clients (may be 1 or more clients). They all should give the following output:
Starting...
A
B
C
D
E
F
...
...
Done!
They all give the above output without using multi-thread. But using multi-threading, it gives the above output only when there're 1 server and 1 client.
Here is the server code. Only the important part is shown; TODO
comment to indicate send/receive data. Common.totalClients
is the number of clients to be connected with.
class ServerMain {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket[] sockets = new Socket[Common.totalClients];
ObjectOutputStream[] sendStreams = new ObjectOutputStream[Common.totalClients];
ObjectInputStream[] receiveStreams = new ObjectInputStream[Common.totalClients];
SendThread[] sendThreads = new SendThread[Common.totalClients];
ReceiveThread[] receiveThreads = new ReceiveThread[Common.totalClients];
// ... (here, all assignment of the above variables and closing serverSocket)
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients;
for (char sendChar = 'A'; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// sending server data to all clients
for (int i = 0; i < Common.totalClients; i++) {
sendThreads[i].send(sendChar); // TODO
//Common.send(sendStreams[i], sendChar);
}
System.out.println(sendChar);
for (int i = 0; i < Common.totalClients; i++) {
char receivedChar = receiveThreads[i].receive(); // TODO
//char receivedChar = Common.receive(receiveStreams[i]);
// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
sendThreads[i].send(receivedChar); // TODO
//Common.send(sendStreams[j], receivedChar);
}
System.out.println(receivedChar);
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
// ... (here, closing all sockets and interrupt all threads)
System.out.println("Done!");
}
}
Here is the client code (only important part). First client has clientID
of 1. Second client has clientID
of 2 and such that. And first client should be run first, then second and such that. TODO
comment to indicate send/receive data.
System.out.println("Starting...");
final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients + clientID;
for (char sendChar = 'A' + clientID; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {
// receiving data from server and other clients whose "clientID" is less than this client's "clientID" (via server)
for (int j = 0; j < clientID; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
// sending this client's data
sendThread.send(sendChar); // TODO
//Common.send(sendStream, sendChar);
System.out.println(sendChar);
// receiving data from other clients whose "clientID" is greater than this client's "clientID" (via server)
for (int j = clientID; j < Common.totalClients; j++) {
System.out.println(receiveThread.receive()); // TODO
//System.out.println(Common.receive(receiveStream));
}
try { Thread.sleep(Common.loopSleep); }
catch (InterruptedException e) { e.printStackTrace(); }
}
I don't know which is the culprit of not getting the expected output when using multi-thread. Again, using multi-thread, it works only for 1 client (and the server)!
Here is ReceiveThread
. Note, both the server and the clients are stuck at try { ch = queue.take(); }
if more than 1 client are connected.
class ReceiveThread extends Thread {
private ObjectInputStream receiveStream;
private BlockingQueue<Character> queue = new ArrayBlockingQueue<Character>(Common.totalClients);
public ReceiveThread(ObjectInputStream receiveStream) {
this.receiveStream = receiveStream; start();
}
public void run() {
while (!Thread.interrupted()) {
try { queue.put(receiveStream.readChar()); }
catch (InterruptedException e) { return; }
catch (IOException e) { return; }
}
}
public char receive() {
char ch = '#';
try { ch = queue.take(); }
catch (InterruptedException e) { e.printStackTrace(); }
return ch;
}
}
Here is SendThread
code:
class SendThread extends Thread {
private ObjectOutputStream sendStream;
private volatile boolean pending = false;
private volatile char sendChar;
public SendThread(ObjectOutputStream sendStream) {
this.sendStream = sendStream; start();
}
public void run() {
while (!Thread.interrupted()) {
if (pending) {
pending = false;
try {
sendStream.writeChar(sendChar);
sendStream.flush();
} catch (IOException e) { return; }
}
try { Thread.sleep(10); }
catch (InterruptedException e) { return; }
}
}
public void send(char ch) {
sendChar = ch; pending = true;
}
}
Now, if Common.totalClient
is 2 (ie 2 clients to run), then I get the following output:
Server: (Runs first)
Starting...
A
Client 1 (clientID
is 1): (Runs after the server)
Starting...
A
B
B
Client 2 (clientID
is 2): (Runs after Client 1)
Starting...
A
They are stuck at there, even no exception. Why is this behaviour? How to solve it? Note that I have used the same SendThread
and ReceiveThread
classes by which I have succcessfully implemented P2P communication. Feel free to ask more detailed code which I have used here if you have concern.
Edit:
For convenience, I've added the full runnable project (which only contains 5 small .java files: 2 thread classes; server, client classes and common class). It is currently faulty when using additional threads. But it works as expected without additional threads. To test it without the additional threads, please just do: 1. Comment \\ TODO
lines, 2. Uncomment the single lines just after \\ TODO
lines. 3. Comment the additional thread construction lines (4 lines). Here is the link: (I've deleted the link because it is not needed to solve the problem!)