Another complete overhaul of client-server communication:

-Added broadcastAnnouncement message to ClientHandler
-Bugfix: "Connection lost" at startup
-Added LOGIN$username to protocol
This commit is contained in:
Jonas 2022-04-06 21:57:21 +02:00
parent 51d969e298
commit c64c754d22
7 changed files with 77 additions and 43 deletions

View File

@ -20,16 +20,24 @@ public class Client {
private Socket socket;
private BufferedReader in;
private BufferedWriter out;
public String userName;
public ClientPinger clientPinger;
public Client(Socket socket, String userName) {
public Client(Socket socket) {
try {
this.socket = socket;
this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.in = new BufferedReader((new InputStreamReader((socket.getInputStream()))));
this.userName = userName;
sendMsgToServer(getUsername()); //todo: dont just send username directly pls
//sending the initial name to server.
String systemName;
try {
systemName = System.getProperty("user.name");
} catch (Exception e) {
systemName = "Unknown User";
}
if (systemName == null) systemName = "Unknown User";
sendMsgToServer("LOGON$" + systemName);
clientPinger = new ClientPinger(this, this.socket);
} catch (IOException e) {
e.printStackTrace();
@ -53,9 +61,7 @@ public class Client {
sendMsgToServer(formattedMSG);
}
Thread.sleep(20);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
//LOGGER.debug("just checked next line");
@ -67,12 +73,10 @@ public class Client {
/**
* Starts a thread which listens for incoming messages
* Starts a thread which listens for incoming chat messages / other messages that the user
* has to see
*/
public void chatListener() {
/*TODO: what type of decoding has to be done
TODO how shall input be logged?
*/
new Thread(new Runnable() {
@Override
public void run() {
@ -144,19 +148,18 @@ public class Client {
String hostname;
int port = 42069; //can be set via argument later if needed.
if (args.length < 1) {
System.out.println("Enter the host's IP address (or type localhost)");
System.out.println("Enter the host's IP address (or type l for localhost)");
hostname = sc.next();
if (hostname == "l") {
hostname = "localhost";
}
} else {
hostname = args[0];
}
String systemName = System.getProperty("user.name");
System.out.println("Choose a nickname (Suggestion: " + systemName
+ "): "); //Suggests a name based on System username
String username = sc.next();
Socket socket;
try {
socket = new Socket(hostname, 42069);
Client client = new Client(socket, username);
Client client = new Client(socket);
client.chatListener();
Thread cP = new Thread(client.clientPinger);
cP.start();
@ -169,10 +172,6 @@ public class Client {
}
public String getUsername() {
return userName;
}
public Socket getSocket() {
return socket;
}

View File

@ -16,11 +16,13 @@ public class JClientProtocolParser {
* @param c this Client(required so this method can access the Client's methods)
*/
public static void parse(String msg, Client c) {
//LOGGER.debug("got message: " + msg + ".");
String header = ""; //"header" is the first 5 characters, i.e. the protocol part
try {
header = msg.substring(0, 5);
} catch (IndexOutOfBoundsException e) {
System.out.println("Received unknown command");
e.printStackTrace();
}
switch (header) {
case "SPING":
@ -40,7 +42,7 @@ public class JClientProtocolParser {
break;
case "QUITC":
c.closeEverything();
System.out.println("bye!");
break;
default:
System.out.println("Received unknown command");
}

View File

@ -36,7 +36,7 @@ public class ClientPinger implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000);
Thread.sleep(20000);
while (socket.isConnected() && !socket.isClosed()) {
gotPingBack = false;
client.sendMsgToServer("CPING");

View File

@ -1,6 +1,7 @@
Client commands:
Client to server commands:
Implemented:
* LOGON$name Log on, with the name set to whatever follow $.
* CHATA$message Send chat message to all
* QUITS request to quit server/ leave server. The server should then remove the
relevant ClientHandler and close the sockets, but before doing so, it sends
@ -20,7 +21,7 @@ Future / planned:
* VOTEH humans voting who is the ghost
* LISTP list players/clients in session with the Server
Server Commands:
Server to client Commands:
Implemented:
* SPING Ping from server to client

View File

@ -33,19 +33,21 @@ public class ClientHandler implements Runnable {
try {
this.socket = socket;
this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.in = new BufferedReader((new InputStreamReader((socket.getInputStream()))));
this.clientUserName = in.readLine();
// duplicate handling: if username already taken, assign random name to client
this.in = new BufferedReader(new InputStreamReader((socket.getInputStream())));
this.clientUserName = "Mysterious Passenger"; //todo: duplicate handling for this
/*
// todo: duplicate handling more elegantly
if (AllClientNames.allNames("").contains(clientUserName)) {
clientUserName = NameGenerator.randomName(clientUserName);
}
// add username to list of all client names for future duplicate checking
AllClientNames.allNames(clientUserName);
*/
connectedClients.add(this);
serverPinger = new ServerPinger(out, socket);
Thread sP = new Thread(serverPinger);
sP.start();
broadcastChatMessage("SERVER: " + clientUserName + " has joined the Server");
} catch (IOException e) {
e.printStackTrace();
}
@ -109,23 +111,52 @@ public class ClientHandler implements Runnable {
* @param newName The desired new name to replace the old one with.
*/
public void changeUsername(String newName) {
if (AllClientNames.allNames("").contains(newName)) {
if (AllClientNames.allNames("").contains(newName)) { //todo: more elegant solution
newName = NameGenerator.randomName(newName);
}
String h = this.clientUserName; //just a friendly little helper
this.clientUserName = newName;
AllClientNames.allNames(newName);
broadcastChatMessage(h + " have changed their nickname to " + clientUserName);
broadcastAnnouncement(h + " has changed their nickname to " + clientUserName);
}
/**
* Broadcasts a Message to all active clients in the form "Username: msg"
* Sets the client's username on login, if the username is already taken, a similar
* option is chosen. Functionally, the only difference between this method and changeUsername
* is that it doesn't print out the name change.
*
* @param msg the Message to be broadcasted
* @param name The desired name.
*/
public void setUsernameOnLogin(String name) {
//todo: duplicate checking
this.clientUserName = name;
broadcastAnnouncement( clientUserName + " has joined the Server");
//todo: add this name to namelist
}
/**
* Broadcasts a Message to all active clients in the form "Username: @msg"
*
* @param msg the Message to be broadcast
*/
public void broadcastChatMessage(String msg) {
for (ClientHandler client : connectedClients) {
client.sendMsgToClient("CHATM$" + clientUserName + ": \"" + msg + "\"");
client.sendMsgToClient("CHATM$" + clientUserName + ": " + msg);
}
}
/**
* Broadcasts a non-chat Message to all active clients. This can be used for server
* messages / announcements rather than chat messages. The message will be printed to the user ex-
* actly as it is given to this method. Unlike broadcastChatMessage, it will also be printed onto
* the server console.
*
* @param msg the Message to be broadcast
*/
public void broadcastAnnouncement(String msg) {
System.out.println(msg);
for (ClientHandler client : connectedClients) {
client.sendMsgToClient("CHATM$" + msg);
}
}

View File

@ -21,6 +21,7 @@ public class JServerProtocolParser {
* @param h this ClientHandler (required so this method can access the ClientHandler's methods)
*/
public static void parse(String msg, ClientHandler h) {
//LOGGER.debug("got message: " + msg + ".");
String header = ""; //"header" is the first 5 characters, i.e. the protocol part
try {
header = msg.substring(0, 5);
@ -32,6 +33,14 @@ public class JServerProtocolParser {
//sends chat message to all connected clients
h.broadcastChatMessage(msg.substring(6));
break;
case "LOGON":
//sets name to whatever follows LOGON$
try {
h.setUsernameOnLogin(msg.substring(6));
} catch (Exception e) {
h.setUsernameOnLogin("A Mysterious Passenger");
}
break;
case "NAMEC":
//changes name to whatever follows NAMEC$. If the new name is already in use, it will append
//random numbers to the name.
@ -47,6 +56,7 @@ public class JServerProtocolParser {
break;
case "QUITS":
//safely disconnects the user
h.broadcastAnnouncement(h.getClientUserName() + " has left the server.");
h.disconnectClient();
break;
default:

View File

@ -34,15 +34,6 @@ public class Server {
Thread th = new Thread(nextClient);
connectedClients.add(nextClient);
th.start();
// close socket + remove client if client is disconnected
/* TODO: Modify or remove this disconnection handling - right now, it is not functioning
(when disconnecting there are "broken pipe" exceptions on client side and "stream closed"
exceptions on the server side).
*/
if (nextClient.getSocket().getInputStream().read() == -1) {
System.out.println("client disconnected. closing socket");
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();