I am working on a project where object IO in Java is performed. The issue lies within a relationship between the Client and the Server, on which the object sent by the ObjectOutputStream, located in the Server, is not not equal to the class received by the client.
The ClientLoop class written here does not exist on the Client. (I should probably change that to avoid confusion.) A ClientLoop is created for each Client, from a Socket, that is detected and is established a connection from the server. It listens for the next object sent by the client and sends it down the Object pipeline, where the action is determined.
A question possibly anticipating the underlying technology from the object streams
public class ClientLoop extends Loop implements Sendable {
private final Commander commander;
private final Socket socket;
private final ObjectInputStream input;
private final ObjectOutputStream output;
private final Server server;
public ClientLoop(Server server, Socket socket) throws IOException {
this.server = server;
this.socket = socket;
//we need to construct the output stream first
//to discard the stream header
this.output = new ObjectOutputStream(socket.getOutputStream());
this.input = new ObjectInputStream(socket.getInputStream());
this.commander = new Commander(server, socket, this);
}
@Override
public void loop() {
try {
try {
Object next = input.readObject();
String className = next.getClass().getName();
System.out.println("Server Receive: " + next);
if (className.equals("com.meti.server.util.Command")) {
commander.runCommand(castIfOfInstance(next, Command.class));
//this section of code here says that whenever an AssetChange is being sent by the client.
} else if (next instanceof AssetChange) {
AssetChange assetChange = castIfOfInstance(next, AssetChange.class);
assetChange.update(server.getAssetManager().getAsset(assetChange.getAssetPath()));
//has been change, update all clientloops
} else {
getInstance().log(Level.WARNING, "Found no type handling " +
"for class type " + className);
}
} catch (EOFException e) {
socket.close();
setRunning(false);
}
} catch (Exception e) {
getInstance().log(Level.WARNING, e);
}
}
@Override
public void send(Serializable serializable, boolean flush) throws IOException {
System.out.println("Server Send: " + serializable);
output.writeObject(serializable);
if (flush) {
output.flush();
}
}
}
The Commander class, here, is to provide additional functionality from the ClientLoop class, where the class would be much too difficult to follow. It solely exists to house the method to run the command. It is probably not the best code structure, going to fix that later.
public class Commander {
private Server server;
private Socket socket;
private Sendable sendable;
public Commander() {
}
public Commander(Server server, Socket socket, Sendable sendable) {
this.server = server;
this.socket = socket;
this.sendable = sendable;
}
public void runCommand(Command next) throws IOException {
if ("login".equals(next.getName())) {
String password = (String) next.getArgs()[0];
if (password.equals(server.getPassword())) {
getInstance().log(Level.INFO, "Client " + socket.getInetAddress() + " has connected with valid password");
} else {
getInstance().log(Level.INFO, "Client has invalid password, kicking out!");
socket.close();
}
} else if ("disconnect".equals(next.getName())) {
socket.close();
} else if ("list".equals(next.getName())) {
Cargo<String> cargo = new Cargo<>();
HashMap<String, Asset<?>> assets = server.getAssetManager().getAssets();
ArrayList<String> paths = new ArrayList<>();
paths.addAll(assets.keySet());
cargo.getContents().addAll(paths);
sendable.send(cargo, true);
} else if ("get".equals(next.getName())) {//should be declared other side...
String path = Utility.castIfOfInstance(next.getArgs()[0], String.class);
Asset<?> asset = server.getAssetManager().getAsset(path);
sendable.send(asset, true);
}
}
}
Here's the log from the latest run session. Whenever it says "Server Receive", it means that the server found a sent object from the client, and when it says "Server Send", it would be the sent object by the server, via the ObjectInputStream and ObjectOutputStream. Otherwise, it is a generic console statement. At the end of the log, it shows that the server sends an object called "Server Send:Asset{file=Nexus\sample.txt, content=Hello Server!asdxc}". (Please note, the phrase "asdxc" was created with button mashing during testing. It contains no code significance.)
However, herein lies the problem, in the preceding statement "Receive: Asset{file=Nexus\sample.txt, content=Hello Server!}".
I conclude from this log output, that ObjectInputStream in the client and the ObjectOutputStream in the server are indeed functioning correctly, however the object sent by the ObjectOutputStream on the server is not equal to the object immediately received by the ObjectInputStream on the client. According to the log, there was only one object sent by the server, which was not registered properly by the client.
Oct 31, 2017 8:11:11 AM com.meti.Main log
INFO: Starting application
Oct 31, 2017 8:11:50 AM com.meti.Main log
INFO: Reading directory
Oct 31, 2017 8:11:51 AM com.meti.Main log
INFO: Listening for clients
Oct 31, 2017 8:11:53 AM com.meti.Main log
INFO: Located client at /127.0.0.1
Server Receive: Command{name='login', args=[5875580034436271440]}
Oct 31, 2017 8:11:53 AM com.meti.Main log
INFO: Client /127.0.0.1 has connected with valid password
Server Receive: Command{name='list', args=[files]}
Server Send:com.meti.server.util.Cargo@68917144
Server Receive: Command{name='get', args=[Nexus\sample.txt]}
Server Send:Asset{file=Nexus\sample.txt, content=Hello Server!}
Receive: Asset{file=Nexus\sample.txt, content=Hello Server!}
Server Receive: com.meti.server.asset.text.TextChange@546c91cb
Server Receive: com.meti.server.asset.text.TextChange@1a2f571d
Server Receive: com.meti.server.asset.text.TextChange@7a308ae0
Server Receive: com.meti.server.asset.text.TextChange@5897f82d
Server Receive: com.meti.server.asset.text.TextChange@68c5d83d
Server Receive: com.meti.server.asset.text.TextChange@832ed87
Server Receive: com.meti.server.asset.text.TextChange@76ab11eb
Server Receive: Command{name='get', args=[Nexus\sample.txt]}
Server Send:Asset{file=Nexus\sample.txt, content=Hello Server!asdxc}
Receive: Asset{file=Nexus\sample.txt, content=Hello Server!}
If more code is needed to diagnose the problem, I have the full codebase at this GitHub link: https://github.com/Mathhman/Nexus
If you're sending the same object twice with different content you need to use writeUnshared()
or reset()
to ensure it actually gets sent again.