Network sent Vector only updates once in client?

Okay, so I’m trying to send a vector of objects from server to client. the clients take control of one object from the vector and send it’s x and y to the server vector.

Now the server is running fine from what I can tell. things are updating in real time, but the client only seems to update right once - when it starts.

this is the client code:



import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import java.io.*;
import java.net.*;
import java.util.Vector;

public class SlickClient extends BasicGame{

    ClientThread ct;
    Vector<Player> players;
    Player me;
    int ALL_KEYS = 0xFF;
    boolean keys[];

    public SlickClient()
    {
        super("Test Online Client - by William Starkovich");
    }

    public void init(GameContainer gc) throws SlickException {
        try{
            keys = new boolean[ALL_KEYS];

            for(int i = 0; i < ALL_KEYS; i++){
                keys[i] = false;
            }

            players = new Vector<Player>();

            connect();
        }

        catch(Exception e){

        }
    }

    public void connect(){
        String ip = "127.0.0.1";
        ct = new ClientThread(ip);
        ct.start();
        ct.setPriority(Thread.MAX_PRIORITY);

        me = ct.me;
        players = ct.players;
    }

    public void update(GameContainer gc, int delta)throws SlickException{
        controls();

        players = new Vector<Player>();

        System.out.println("ct size: " + ct.players.size());

        me = ct.me;
        players = ct.players;
    }

    public void render(GameContainer gc, Graphics g) throws SlickException{
        g.setColor(Color.black);
        g.fillRect(0,0,640,480);

        for(int i = 0; i < players.size(); i++){
            g.setColor(Color.cyan);
            g.fillRect(players.get(i).x, players.get(i).y, 50, 50);
        }

        g.drawString("Players: " + players.size(), 50, 10);
    }

    public void keyPressed(int key, char c) {
        keys[key] = true;
    }

    public void keyReleased(int key, char c) {
        keys[key] = false;
    }

    public void controls(){
        if(keys[Input.KEY_UP]){
            me.y--;
        }

        else if(keys[Input.KEY_DOWN]){
            me.y++;
        }

        else if(keys[Input.KEY_LEFT]){
            me.x--;
        }

        else if(keys[Input.KEY_RIGHT]){
            me.x++;
        }
    }

    public static void main(String[] args) throws SlickException{
     AppGameContainer app =
        new AppGameContainer( new SlickClient() );

     app.setShowFPS(false);
     app.setAlwaysRender(true);
     app.setTargetFrameRate(60);
     app.setDisplayMode(800, 600, false);
     app.start();
    }
}

class ClientThread extends Thread implements Runnable{
    Socket socket;
    Vector<Player> players;
    int playerID;
    Player me;
    DataOutputStream out;
    ObjectInputStream in;
    boolean loop = true;

    @SuppressWarnings("unchecked")
    public ClientThread(String ip){
        super("ClientThread");

        try{
            this.players = new Vector<Player>();
            socket = new Socket(ip, 4444);
            socket.setTcpNoDelay(true);
            out = new DataOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
            playerID = in.readInt(); 
            this.players = (Vector<Player>) in.readObject();

            if(this.players != null)
                System.out.println("Not Null: " + this.players.size());

            boolean b = false;
            for(int i = 0; i < this.players.size(); i++){
                if(!b){
                    if(this.players.get(i).id == playerID){
                        me = this.players.get(i);
                        b = true;
                    }
                }
            }
        }

        catch(Exception e){
            e.printStackTrace();
        }
    }

    public void run(){
        try{
            while(loop){
                try{
                    if(!socket.isClosed() && socket.isConnected()){
                        out.writeInt(me.x);
                        out.writeInt(me.y);
                        out.flush();

                        this.players = new Vector<Player>();
                        this.players = (Vector<Player>) in.readObject();

                        System.out.println("size" + players.size());
                        sleep(15);
                    }

                    else
                        loop = false;

                }
                catch(Exception e){
                        e.printStackTrace();
                        socket.close();
                }  
            }



        }

        catch(Exception e){
            e.printStackTrace();
        }
    }
}


Edit: server code:


import java.net.*;
import java.util.Vector;
import java.io.*;
import javax.swing.*;
import java.io.Serializable;

public class SlickServer extends JFrame{

    private static final long serialVersionUID = 1L;

    JTextArea textArea;
    JScrollPane scrollPane;

    public SlickServer(){
        super("Test Online Server - by William Starkovich");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(500, 100);
        setLocationRelativeTo(null);

        textArea = new JTextArea();

        scrollPane = new JScrollPane(textArea);

        //getContentPane().add(textArea);
        getContentPane().add(scrollPane);
    }

    public static void main(String[] args) throws IOException {

        Vector<Player> player = new Vector<Player>();

        SlickServer ss = new SlickServer();
        ss.setVisible(true);

        ServerSocket serverSocket = new ServerSocket(4444);
        boolean listening = true;

        int playerID = 0;

        while(listening){
            ss.textArea.append("Waiting to connect with player: " + playerID  + "\n");
            new ClientThread(serverSocket.accept(), player, playerID, ss.textArea).start();
            playerID++;
            ss.textArea.append("Players: " + player.size() + "\n");
        }

        serverSocket.close();
        System.exit(0);
    }
}


class ClientThread extends Thread implements Runnable{
    int playerID;
    Socket acceptedSocket;
    JTextArea textArea;
    Vector<Player> players;
    Player me;

    public ClientThread(Socket acceptedSocket, Vector<Player> players, int playerID, JTextArea textArea){
        super("ClientThread");
        this.acceptedSocket = acceptedSocket;
        this.players = players;
        players.add(new Player(50,50, playerID));

        if(players != null)
            System.out.println("Not Null: " + players.size());

        boolean b = false;
        for(int i = 0; i < players.size(); i++){
            if(!b){
                if(players.get(i).id == playerID){
                    me = players.get(i);
                    b = true;
                }
            }
        }

        this.playerID = playerID;
        this.textArea = textArea;
    }

    public void run(){
        try{
            Socket clientSocket = acceptedSocket;
            clientSocket.setTcpNoDelay(true);
            textArea.append("Accepted. Now creating I/O.\n");
            DataInputStream in = new DataInputStream(clientSocket.getInputStream());
            ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
            textArea.append("player connected. \n");
            out.writeInt(me.id);
            out.writeObject(players);
            out.flush();

            while(!clientSocket.isClosed() && clientSocket.isConnected()){

                me.x = in.readInt();
                me.y = in.readInt();

                if(players != null)
                    System.out.println("Not Null: " + players.size());


                textArea.append("PlayerID: " + playerID + " Players: " + players.size() + " me.x: " + me.x + " me.y: " + me.y + "\n");

                out.writeObject(players);
                out.flush();

               sleep(15);
            }

        }

        catch(Exception e){
            e.printStackTrace();
            System.exit(1);
        }


    }

}


class Player implements Serializable{
    private static final long serialVersionUID = 1L;
    int x;
    int y;
    int id;

    public Player(int x, int y, int id){
        this.x = x;
        this.y = y;
        this.id = id;
    }
}


Added server code.

I don’t even see you using the InputStream in the client anywhere?


class ClientThread extends Thread implements Runnable{
...
     public ClientThread(String ip){
         ...
         in = new ObjectInputStream(socket.getInputStream());
         ...
         this.players = (Vector<Player>) in.readObject();
    }
...


    public void run(){
       ...
                    if(!socket.isClosed() && socket.isConnected()){
                        ...
                        this.players = (Vector<Player>) in.readObject();
                    }
}

Tried taking out the multi-threading from the client. It worked, but I still got the same errors:




import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import java.io.*;
import java.net.*;
import java.util.Vector;

public class SlickClient extends BasicGame{
	
	//ClientThread ct;
	Vector<Player> players;
	Player me;
	int ALL_KEYS = 0xFF;
	boolean keys[];
	
	Socket socket;
	int playerID;
	DataOutputStream out;
    ObjectInputStream in;
    boolean loop = true;
	
	public SlickClient()
    {
        super("Test Online Client - by William Starkovich");
        
        
    }
	
	public void init(GameContainer gc) throws SlickException {
		try{
			keys = new boolean[ALL_KEYS];
			
			for(int i = 0; i < ALL_KEYS; i++){
				keys[i] = false;
			}
			
			players = new Vector<Player>();
			
			
			//connect();
		}
		
		catch(Exception e){
			
		}
		
		try{
			this.players = new Vector<Player>();
			socket = new Socket("127.0.0.1", 4444);
			socket.setTcpNoDelay(true);
			out = new DataOutputStream(socket.getOutputStream());
	        in = new ObjectInputStream(socket.getInputStream());
			playerID = in.readInt(); 
			this.players = (Vector<Player>) in.readObject();
			
			if(this.players != null)
				System.out.println("Not Null: " + this.players.size());
			
			boolean b = false;
			for(int i = 0; i < this.players.size(); i++){
				if(!b){
					if(this.players.get(i).id == playerID){
						me = this.players.get(i);
						b = true;
					}
				}
			}
		}
		
		catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public void update(GameContainer gc, int delta)throws SlickException{
		controls();
		
		try{
			if(!socket.isClosed() && socket.isConnected()){
        		out.writeInt(me.x);
        		out.writeInt(me.y);
	        	out.flush();
	        	
	        	this.players = new Vector<Player>();
	        	this.players = (Vector<Player>) in.readObject();
	        	
	        	System.out.println("size" + players.size());
	        	//sleep(15);
			}
			
			else
				loop = false;
		
        }
		catch(Exception e){
	    		e.printStackTrace();
	    		//socket.close();
	    }  
		
		//players = new Vector<Player>();
		
		//System.out.println("ct size: " + ct.players.size());

	}
	
	public void render(GameContainer gc, Graphics g) throws SlickException{
		g.setColor(Color.black);
		g.fillRect(0,0,640,480);
		
		for(int i = 0; i < players.size(); i++){
			g.setColor(Color.cyan);
			g.fillRect(players.get(i).x, players.get(i).y, 50, 50);
		}
		
		g.drawString("Players: " + players.size(), 50, 10);
	}
	
	public void keyPressed(int key, char c) {
		keys[key] = true;
	}
	
	public void keyReleased(int key, char c) {
		keys[key] = false;
	}
	
	public void controls(){
		if(keys[Input.KEY_UP]){
			me.y--;
		}
		
		else if(keys[Input.KEY_DOWN]){
			me.y++;
		}
		
		else if(keys[Input.KEY_LEFT]){
			me.x--;
		}
		
		else if(keys[Input.KEY_RIGHT]){
			me.x++;
		}
	}
	
	public static void main(String[] args) throws SlickException{
	 AppGameContainer app =
		new AppGameContainer( new SlickClient() );
	
	 app.setShowFPS(false);
	 app.setAlwaysRender(true);
	 app.setTargetFrameRate(60);
	 app.setDisplayMode(800, 600, false);
	 app.start();
	}
}

going to revert it back to multi-threading though.

Can you be more specific on what the client “see’s” from the server? Does the client never even see other players connect when it reads in the new vector? Does it only see its own position changing? Does it see any changes coming after each poll of the server, or does it even correctly loop and read in 2nd and 3rd states etc.

Are you absolutely positive that the server is working correctly? Does one thread for player A in the server see changes to player B’s position as B sends in updates, or do you only know that player B’s thread sees B sending in updates. Are you sure the server is sending the correct state vector back to the client?

Given your recent questions, I have to recommend taking a step back from multi-threading like this. There are a lot of memory complications at risk. One thread can see old memory values of variables updated by another thread, etc. unless you adequately protect yourself. The only thing I’m seeing of that is that Vector is synchronized, which might inadvertently be making your server appear safe.

the client only gets the server’s player vector. It only gives it’s x and y to the player object inside the vector with the same id.

the server threads see the changes from player vector because the test text shows size 2 on id 1 when there’s 2 connections. id 2 also shows 2 in size.

 textArea.append("PlayerID: " + playerID + " Players: " + players.size() + " me.x: " + me.x + " me.y: " + me.y + "\n");

For some reason I have to add reset before each time I write to the OOS.

code:


out.reset();
out.writeObject(players);
out.flush();

I remember I’ve had some problems with serializing Vector objects over the Object streams as well.
Maybe serializing and deserializing the Vectors through Data streams will solve the issue.

ObjectOutputStream keep track all object’s references , until you invoke close() or reset().
So I you keep it as a long-lived OutputStream, invoke frequently #reset() for clear it’s internal handle table.

The ObjectInputStream can has the same negate effect, it retain an handletable when it perform deserialization.
You can’t manually discard it’s handle table without closing the input stream.
But when you reset the OutputStream a TC_RESET is push on the stream that will be handled by the ObjectInputStream.
So, each time you reset the outputstream, the inputstream will perform also the reset in order to be in sync with their handles table.

So, the reset is a good thing, you MUST do it in order to preserve your memory and let the garbage collector done it’s job.

You didn’t listen, of course the Vector will correctly report changes to its sizes. It’s synchronized internally, so any add() will properly update the value returned by size(). That is not a good measurement of how correct your server is working. Because of threading-memory issues it is possible that each thread saw a different version of the player’s coordinates. Thus any vector sent to the player would be wrong, even if it properly said it had a size of 2.

But, I think divxdede is probably right in this case so you can go about your way, using threads in a dangerous manner.

The reset can be also an error in the deserialization process.

Studying this example:

   Client:    Point playerLocation = getPlayerLocation();   // give instance A with x=22 , y=45
                 oos.write( playerLocation );

   Server    Point playerLocation = (Point)ois.readObject(); // give instance B (deserialized) with x=22, y =45

    // stay a while

   Client:    Point playerLocation = getPlayerLocation();   // give instance A with x=30 , y=46
                 oos.write( playerLocation );

   Server    Point playerLocation = (Point)ois.readObject(); // give instance B (deserialized) with x=22, y =45
                 // You don't have the new location because the handletable was not reseted.
                 // When the client sent the playerLocation, the ObjectOutputStream know it had already serialized this instance
                 // and just push on the stream the "ref-handle" that is an identifier.
                 // The object input stream read this ref-handle and return the previously deserialized instance.
                 // This mecanism is done for preserve instance topology

So if you write a new time the same instance that has muted to the server, you must reset the ObjectOutputStream before.
Now, maybe you have also a threading issue and needs synchronization.