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 Socket socket;
private BufferedReader in; private BufferedReader in;
private BufferedWriter out; private BufferedWriter out;
public String userName;
public ClientPinger clientPinger; public ClientPinger clientPinger;
public Client(Socket socket, String userName) { public Client(Socket socket) {
try { try {
this.socket = socket; this.socket = socket;
this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.in = new BufferedReader((new InputStreamReader((socket.getInputStream())))); 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); clientPinger = new ClientPinger(this, this.socket);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -53,9 +61,7 @@ public class Client {
sendMsgToServer(formattedMSG); sendMsgToServer(formattedMSG);
} }
Thread.sleep(20); Thread.sleep(20);
} catch (IOException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
//LOGGER.debug("just checked next line"); //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() { public void chatListener() {
/*TODO: what type of decoding has to be done
TODO how shall input be logged?
*/
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -144,19 +148,18 @@ public class Client {
String hostname; String hostname;
int port = 42069; //can be set via argument later if needed. int port = 42069; //can be set via argument later if needed.
if (args.length < 1) { 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(); hostname = sc.next();
if (hostname == "l") {
hostname = "localhost";
}
} else { } else {
hostname = args[0]; 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; Socket socket;
try { try {
socket = new Socket(hostname, 42069); socket = new Socket(hostname, 42069);
Client client = new Client(socket, username); Client client = new Client(socket);
client.chatListener(); client.chatListener();
Thread cP = new Thread(client.clientPinger); Thread cP = new Thread(client.clientPinger);
cP.start(); cP.start();
@ -169,10 +172,6 @@ public class Client {
} }
public String getUsername() {
return userName;
}
public Socket getSocket() { public Socket getSocket() {
return socket; 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) * @param c this Client(required so this method can access the Client's methods)
*/ */
public static void parse(String msg, Client c) { 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 String header = ""; //"header" is the first 5 characters, i.e. the protocol part
try { try {
header = msg.substring(0, 5); header = msg.substring(0, 5);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
System.out.println("Received unknown command"); System.out.println("Received unknown command");
e.printStackTrace();
} }
switch (header) { switch (header) {
case "SPING": case "SPING":
@ -40,7 +42,7 @@ public class JClientProtocolParser {
break; break;
case "QUITC": case "QUITC":
c.closeEverything(); c.closeEverything();
System.out.println("bye!"); break;
default: default:
System.out.println("Received unknown command"); System.out.println("Received unknown command");
} }

View File

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

View File

@ -1,6 +1,7 @@
Client commands: Client to server commands:
Implemented: Implemented:
* LOGON$name Log on, with the name set to whatever follow $.
* CHATA$message Send chat message to all * CHATA$message Send chat message to all
* QUITS request to quit server/ leave server. The server should then remove the * 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 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 * VOTEH humans voting who is the ghost
* LISTP list players/clients in session with the Server * LISTP list players/clients in session with the Server
Server Commands: Server to client Commands:
Implemented: Implemented:
* SPING Ping from server to client * SPING Ping from server to client

View File

@ -33,19 +33,21 @@ public class ClientHandler implements Runnable {
try { try {
this.socket = socket; this.socket = socket;
this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.in = new BufferedReader((new InputStreamReader((socket.getInputStream())))); this.in = new BufferedReader(new InputStreamReader((socket.getInputStream())));
this.clientUserName = in.readLine(); this.clientUserName = "Mysterious Passenger"; //todo: duplicate handling for this
// duplicate handling: if username already taken, assign random name to client /*
// todo: duplicate handling more elegantly
if (AllClientNames.allNames("").contains(clientUserName)) { if (AllClientNames.allNames("").contains(clientUserName)) {
clientUserName = NameGenerator.randomName(clientUserName); clientUserName = NameGenerator.randomName(clientUserName);
} }
// add username to list of all client names for future duplicate checking // add username to list of all client names for future duplicate checking
AllClientNames.allNames(clientUserName); AllClientNames.allNames(clientUserName);
*/
connectedClients.add(this); connectedClients.add(this);
serverPinger = new ServerPinger(out, socket); serverPinger = new ServerPinger(out, socket);
Thread sP = new Thread(serverPinger); Thread sP = new Thread(serverPinger);
sP.start(); sP.start();
broadcastChatMessage("SERVER: " + clientUserName + " has joined the Server");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -109,23 +111,52 @@ public class ClientHandler implements Runnable {
* @param newName The desired new name to replace the old one with. * @param newName The desired new name to replace the old one with.
*/ */
public void changeUsername(String newName) { public void changeUsername(String newName) {
if (AllClientNames.allNames("").contains(newName)) { if (AllClientNames.allNames("").contains(newName)) { //todo: more elegant solution
newName = NameGenerator.randomName(newName); newName = NameGenerator.randomName(newName);
} }
String h = this.clientUserName; //just a friendly little helper String h = this.clientUserName; //just a friendly little helper
this.clientUserName = newName; this.clientUserName = newName;
AllClientNames.allNames(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) { public void broadcastChatMessage(String msg) {
for (ClientHandler client : connectedClients) { 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) * @param h this ClientHandler (required so this method can access the ClientHandler's methods)
*/ */
public static void parse(String msg, ClientHandler h) { 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 String header = ""; //"header" is the first 5 characters, i.e. the protocol part
try { try {
header = msg.substring(0, 5); header = msg.substring(0, 5);
@ -32,6 +33,14 @@ public class JServerProtocolParser {
//sends chat message to all connected clients //sends chat message to all connected clients
h.broadcastChatMessage(msg.substring(6)); h.broadcastChatMessage(msg.substring(6));
break; 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": case "NAMEC":
//changes name to whatever follows NAMEC$. If the new name is already in use, it will append //changes name to whatever follows NAMEC$. If the new name is already in use, it will append
//random numbers to the name. //random numbers to the name.
@ -47,6 +56,7 @@ public class JServerProtocolParser {
break; break;
case "QUITS": case "QUITS":
//safely disconnects the user //safely disconnects the user
h.broadcastAnnouncement(h.getClientUserName() + " has left the server.");
h.disconnectClient(); h.disconnectClient();
break; break;
default: default:

View File

@ -34,15 +34,6 @@ public class Server {
Thread th = new Thread(nextClient); Thread th = new Thread(nextClient);
connectedClients.add(nextClient); connectedClients.add(nextClient);
th.start(); 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) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();