Reputation: 157
I have a java client that is constantly every 33 ms updating a class in my game program. the problem is when i open two clients that receive the same class and should be synced they have different results depending on who started or made contact with the server first. Then after the values never change but i (now) know the server is sending out the write class to both. In my tutors lecture notes it says to do it like this
ObjectInput input;
input = new ObjectInputStream(socket.getInputStream());
serializableobject = (cast) input.readObject();
input.flush();
which is how i was taught but now ive come to implement it (didnt need this at uni) it doesnt work. Ive spent literraly weeks and months trying to work out why and today think i cracked why but dont know how to fix it. Bassically from the start input.flush(); doesnt seem to exist for ObjectInput or ObjectInputStream so I left it out and tested it and my class was displayed lovely using break point to verify my server class was sent and set all the variables of my games settings class. the problems come on the next update of this class from the server but its taken me ages to work out. What i believe is happening (if not im gunna give up)is that it reads the very first object sent and holds it in the input stream. Then when i call the same method it continually just gives me the 1st object back again. Im assuming this is because i dont flush it like my tutor says in the lecture slides. The outputstream has a flush which i use. I also have looked in both ObjectInput and ObjectInputStream and wasent able to see flush or an alternative or a way of moving on to the next object in the stream. the latter i dont think would be a good idea anyway becuase the stream will just continual grow until possibly running out of memory i assume. anyway heres my receiving class and my server.:
import java.io.*;
import java.net.*;
import java.util.*;
public class BaseServer
{
private String result = null;
//for serializable class input stream
ObjectInput input;
// Declare client socket
Socket clientSocket = null;
// Declare output stream and string to send to server
DataOutputStream os = null;
// Declare input stream from server and string to store input received from server
BufferedReader is = null;
String responseLine;
//get ip
String serverAddress ;
// Create a socket on port 5000 and open input and output streams on that socket
public void setUpNetwork(String serverAd)
{
try
{
serverAddress = serverAd;
clientSocket = new Socket(serverAddress, 5000);
//string
os = new DataOutputStream(clientSocket.getOutputStream());
is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//serial
input = new ObjectInputStream( clientSocket.getInputStream() );
}
catch (UnknownHostException e)
{
System.err.println("Don't know about host: hostname");
}
catch (IOException e)
{
System.err.println("Couldn't get I/O for the connection to: hostname");
}
}
/*
* Used to communicate with server
* takes message to send and the object expecting the response
* 1 simple method to replace all but one of the below v1 methods
*/
public BaseSerialDataObjects serverTalk(String message){
sendStringMessage(message);
try {
BaseSerialDataObjects bSO = (BaseSerialDataObjects) input.readObject();
return bSO;
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
////////Old v.1 methods for interacting with servers. I have now changed my mind
//and am using strings to send messages and serializable objects as return messages
//I have left these in as I have plans to reuse the most of the code in this game
public String read(){
// Write data to the socket
if (clientSocket != null && os != null && is != null)
{
try
{
os.writeBytes("ok \n");
return is.readLine(); // my bit
}
catch (UnknownHostException e)
{
System.err.println("Trying to connect to unknown host: " + e);
}
catch (IOException e)
{
System.err.println("IOException: " + e);
}
}
return responseLine;
}
//Sends messages to the server
public void sendStringMessage(String message){
// Write data to the socket
if (clientSocket != null && os != null && is != null){
try {
os.writeBytes( message + "\n" );
}
catch (UnknownHostException e){
System.err.println("Trying to connect to unknown host: " + e);
}
catch (IOException e) {
System.err.println("IOException: " + e);
}
}
}
/*
* V.1 method idea am leaving in for an option in future games
*/
public String getStringResult(String returnMessage)
{
String tempHolder = read();
if(tempHolder!=null)
{
String[] array = tempHolder.split("\\s+");
if(array[0].trim().equalsIgnoreCase(returnMessage))
{
result = null;
tempHolder = "";
for(int i = 1; i < array.length;i++)
{
tempHolder = tempHolder + array[i] + " ";
}
return tempHolder.trim();
}
else return null;
}
else
{
return null;
}
}
public synchronized void setResult(String res)
{
result = res;
}
public void closeConnections()
{
// Close the input/output streams and socket
try{
os.close();
is.close();
clientSocket.close();
}catch(Exception e)
{
System.err.println("Exception: " + e);
}
}
}
the line: BaseSerialDataObjects bSO = (BaseSerialDataObjects) input.readObject();
is the one where i put a breakpoint on the return bSO line below and it always shows a class with the same values as the first even though ive just put breakpoint on server and checked its got different values just before returning the class which makes the client hang while i inspect it so i know its the right thread and class and everything
Server :
import java.io.*;
import java.net.*;
import java.*;
import java.util.regex.*;
public class TCPserver implements Runnable
{
static Socket server = null;
private static final int possibleNumberOfPlayers = 8;
private static final int amountOfPlayerInfoHeld = 6;
private int threadNumber;
private static ServerSettings gameSettings = new ServerSettings();
private static int numberOfSettings = 4;
//position 0 = name;
//position 1 = angle;
//position 2 = position x
//position 3 = position y
//position 4 = state
//position 5 = state in relation to server
//POssible states:
// connected
static PlayerPositionsSerial positions = new PlayerPositionsSerial();
public static void main( String args[] )
{
positions.playersArray = new String [possibleNumberOfPlayers][amountOfPlayerInfoHeld];
// Declare a server socket and a client socket for the server
ServerSocket service = null;
// Try to open a server socket on port 5000
try
{
service = new ServerSocket(5000);
server = service.accept();
Thread t0 = new Thread (new TCPserver(0));
t0.start();
server = service.accept();
Thread t1 = new Thread (new TCPserver(1));
t1.start();
server = service.accept();
Thread t2 = new Thread (new TCPserver(2));
t2.start();
server = service.accept();
Thread t3 = new Thread (new TCPserver(3));
t3.start();
server = service.accept();
Thread t4 = new Thread (new TCPserver(4));
t4.start();
server = service.accept();
Thread t5 = new Thread (new TCPserver(5));
t5.start();
server = service.accept();
Thread t6 = new Thread (new TCPserver(6));
t6.start();
server = service.accept();
Thread t7 = new Thread (new TCPserver(7));
t7.start();
/*server = service.accept();
Thread t8 = new Thread (new TCPserver(8));
t8.start(); */
}
catch (IOException e)
{
System.out.println(e + "Error B");
}
}
public void run()
{
// Declare an input stream and String to store message from client
BufferedReader is;
String line;
// Declare an output stream to client
DataOutputStream os;
String thr = Integer.toString(threadNumber);
// Create a socket object from the ServerSocket to listen and accept
// connections. Open input and output streams
try
{
ObjectOutput output;
output = new ObjectOutputStream( server.getOutputStream() );
is = new BufferedReader( new InputStreamReader(
server.getInputStream()));
//if( (line = is.readLine()) != null )
while( (line = is.readLine()) != null )
{
if(line != null)
{
switch(rules(line)){
case "playertable":
output.writeObject( positions );
break;
case "gamesettings":
output.writeObject( gameSettings );
break;
case "servernumber":
StringReturnSerial string = new StringReturnSerial();
string.s = Integer.toString(threadNumber);
output.writeObject(string);
break;
default:
System.out.println("line didnt select anything");
break;
}
}output.flush();
}
// Comment out/remove the stream and socket closes if server is to remain live.
is.close();
}
catch (IOException e)
{
System.out.println(e + "Error B");
}
}
public TCPserver(int tNumber)
{
threadNumber = tNumber;
}
private synchronized void changeArray(int row, int col, String value)
{
positions.playersArray[row][col] = value;
}
private synchronized String readArray(int row, int col)
{
return positions.playersArray[row][col];
}
private String rules(String lineIn)
{
try {
String[] splitArray = lineIn.split("\\s+");
switch(splitArray[0])
{
case "SIGNIN":
positions.playersArray[threadNumber][0] = splitArray[1];
positions.playersArray[threadNumber][4] = "normal";
positions.playersArray[threadNumber][amountOfPlayerInfoHeld-1] = "connected";
addPlayer();
gameSettings.gameStartTime = System.currentTimeMillis() + 10000;
return "gamesettings";
case "ok":
// just for reply, do nothing response heard "ok"
break;
case "MATCHMAKE":
positions.playersArray[threadNumber][amountOfPlayerInfoHeld -1] = "matchmake";
gameSettings.gameState = "matchmake";
return "playertable";
case "READY":
positions.playersArray[threadNumber][amountOfPlayerInfoHeld -1] = "ready";
return "gamesettings";
case "REQUESTSTART":
boolean goAhead = true;
for(int i = 0 ; i < gameSettings.numberOfConnectedPlayers; i++)
{
if(positions.playersArray[i][amountOfPlayerInfoHeld-1] != "ready")
{
goAhead = false;
}
}
if(goAhead)
{
long start = System.currentTimeMillis( );
start = start + 10000;
gameSettings.gameStartTime = start;
}
return "gamesettings";
case "GETPOS":
return "playertable";
case "UPDATEPOS":
//heres where to notice crashes and check for wins etc...
positions.playersArray[threadNumber][1] = splitArray[1];
positions.playersArray[threadNumber][2] = splitArray[2];
positions.playersArray[threadNumber][3] = splitArray[3];
positions.playersArray[threadNumber][4] = splitArray[4];
return "playertable";
case "GETSETTINGS":
return "gamesettings";
/*case "SENDSETTINGS":
// updates settings
for (int i = 1; i < splitArray.length; i++){
switch(i){
case 1:
gameSettings.gameState = splitArray[i];
break;
case 2:
gameSettings.winningNumberOfLaps = Integer.parseInt(splitArray[i]);
break;
case 3:
gameSettings.winString = splitArray[i];
break;
case 4:
gameSettings.gameStartTime = Long.parseLong(splitArray[i]);
break;
case 5:
gameSettings.numberOfConnectedPlayers = Integer.parseInt(splitArray[i]);
break;
}
}
returnString = "gamesettings";
break;
*/
case "GETSERVERNUMBER":
return "servernumber";
case "PLAYING":
gameSettings.gameState = "playing";
break;
case "SERVERPLAYERSTATEUPDATE":
int crashed = 0;
positions.playersArray[Integer.parseInt(splitArray[1])][5] = splitArray[2];
for(int i = 0; i < gameSettings.numberOfConnectedPlayers;){
//takes into account possibility of people leaving game
if(positions.playersArray[i][5] != null){
if(positions.playersArray[i][5] == "explode"){
crashed++;
}
}
}
if(crashed == gameSettings.numberOfConnectedPlayers)
gameSettings.gameState = "explode";
return "gamesettings";
default:
System.err.println("Rule Not Found: " + splitArray[0]);
break;
}
} catch (PatternSyntaxException ex) {
System.err.println("error C: " + ex);
}
return null;
}
public synchronized void addPlayer()
{
gameSettings.numberOfConnectedPlayers++;
}
public synchronized int getNumberOfPlayers()
{
return gameSettings.numberOfConnectedPlayers;
}
public synchronized void removePlayer()
{
gameSettings.numberOfConnectedPlayers--;
}
}
thanks in advance JOhn harris
Upvotes: 2
Views: 7212
Reputation: 3158
In addition to flush
, you need reset
(in ObjectOutputStream). Object streams, read and write, save everything they read and write. Two problems: One, you run out of memory on both sides. Two, if you send the same object twice, the object read will contain the data of the first read. The second time, the write just sends the object ID (essentially), not the data. Call reset after each call to writeObject
.
Upvotes: 1
Reputation: 4184
You shouldn't be reusing the same static socket reference server in each thread.
You want to use the new socket created upon each accept:
Socket newSocket = server.accept();
and pass that to each thread:
Thread t3 = new Thread (new TCPserver(3, newSocket));
Inside of TCPserver then, use ONLY the reference to newSocket.
To force you to do this, get rid of this declaration:
static Socket server = null;
and just make it a local variable in your main method:
Socket server = new ServerSocket(5000);
Here is a server from one of my programs:
private ExecutorService executorService = Executors.newFixedThreadPool(10);
private void acceptConnections() {
listening = true;
while (listening) {
try {
final Socket connection = serverSocket.accept();
System.err.println("SERVER - connection from: " + connection.getInetAddress());
executorService.execute(new ConnectionHandler(connection));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
It uses a thread pool, instead of creating a new thead each time, but the basic idea is the same. My "ConnectionHandler" class is equivalent to your "TCPserver" class.
Upvotes: 1