Applying a lot of unprecedented concepts here, it's a simple chat server capable of handling multiple clients, which are run through JavaFX, and have their individual threads instantiated and handled within the application thread (was more complex than anticipated).
The Chat Server:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.HashSet;
import java.util.Scanner;
public class ChatServer {
private static final int PORT = 9001;
private static HashSet<String> names = new HashSet<>();
private static HashSet<String> userNames = new HashSet<>();
private static HashSet<PrintWriter> writers = new HashSet<>();
private static int usersConnected = 0;
public static void main(String[] args) {
System.out.println(new Date() + "\nChat Server online.\n");
try (ServerSocket chatServer = new ServerSocket(PORT)) {
while (true) {
Socket socket = chatServer.accept();
new ClientHandler(socket).start();
}
} catch (IOException ioe) {}
}
private static String names() {
StringBuilder nameList = new StringBuilder();
for (String name : userNames) {
nameList.append(", ").append(name);
}
return "In lobby: " + nameList.substring(2);
}
private static class ClientHandler extends Thread {
private String name;
private String serverSideName;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("SUBMIT_NAME");
name = in.readLine();
serverSideName = name.toLowerCase();
synchronized (names) {
while (names.contains(serverSideName) || name == null || name.trim().isEmpty()) {
out.println("RESUBMIT_NAME");
name = in.readLine();
serverSideName = name.toLowerCase();
}
}
out.println("NAME_ACCEPTED");
System.out.println(name + " connected. IP: " + socket.getInetAddress().getHostAddress());
messageAll("CONNECT" + name);
userNames.add(name);
names.add(serverSideName);
writers.add(out);
out.println("INFO" + ++usersConnected + names());
while (true) {
String input = in.readLine();
if (input == null || input.isEmpty()) {
continue;
}
messageAll("MESSAGE " + name + ": " + input);
}
} catch (IOException e) {
if (name != null) {
System.out.println(name + " disconnected.");
userNames.remove(name);
names.remove(serverSideName);
writers.remove(out);
messageAll("DISCONNECT" + name);
usersConnected--;
}
} finally {
try {
socket.close();
} catch (IOException e) {}
}
}
}
private static void messageAll(String... messages) {
if (!writers.isEmpty()){
for (String message : messages) {
for (PrintWriter writer : writers) {
writer.println(message);
}
}
}
}
}
The Chat Client:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputDialog;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ChatClient extends Application {
final int PORT = 9001;
final String SERVER_ADDRESS = "localhost";
BufferedReader in;
PrintWriter out;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
TextArea messageArea = new TextArea();
messageArea.setEditable(false);
TextField textField = new TextField();
textField.setEditable(false);
textField.setOnAction(e -> {
out.println(textField.getText());
textField.clear();
});
VBox layout = new VBox(5);
layout.getChildren().addAll(messageArea, textField);
stage.setScene(new Scene(layout));
stage.setTitle("Chatter App By Legato");
Task task = new Task<Void>() {
@Override
public Void call() {
try {
Socket socket = new Socket(SERVER_ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
while (true) {
String line = in.readLine();
if (line.startsWith("SUBMIT_NAME")) {
FutureTask<String> futureTask = new FutureTask<>(new NamePrompt("Choose a screen name:"));
Platform.runLater(futureTask);
try {
out.println(futureTask.get());
} catch(InterruptedException | ExecutionException ex) {}
} else if (line.startsWith("RESUBMIT_NAME")) {
FutureTask<String> futureTask = new FutureTask<>(new NamePrompt("Duplicate name. Try another:"));
Platform.runLater(futureTask);
try {
out.println(futureTask.get());
} catch(InterruptedException | ExecutionException ex) {}
} else if (line.startsWith("NAME_ACCEPTED")) {
textField.setEditable(true);
Platform.runLater(() -> textField.requestFocus());
} else if (line.startsWith("INFO")) {
messageArea.appendText("Users connected: " + line.charAt(4) + '\n' + line.substring(5) + "\n\n");
} else if (line.startsWith("CONNECT")) {
messageArea.appendText(line.substring(7) + " has connected.\n\n");
} else if (line.startsWith("MESSAGE")) {
messageArea.appendText(line.substring(8) + '\n');
} else if (line.startsWith("DISCONNECT")) {
messageArea.appendText(line.substring(10) + " has disconnected.\n\n");
}
}
} catch(IOException ioe) {
messageArea.appendText("Server is offline.\nPlease exit.");
}
return null;
}
};
Thread severIO = new Thread(task);
severIO.setDaemon(true);
severIO.start();
stage.show();
}
class NamePrompt implements Callable<String> {
String message;
NamePrompt(String message) {
this.message = message;
}
@Override
public String call() {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Welcome to Chatter");
dialog.setHeaderText("Screen name selection");
dialog.setContentText(message);
dialog.setGraphic(null);
return dialog.showAndWait().get();
}
}
}
I'd like to focus on:
- General critique on my thread handling.
- The way I handle the client and client input.
- I've also wondering about using
flush
for the streams. It doesn't seem to make a difference but would it increase performance, especially when this is over the net? I've only tested locally, so far. - The Server and Server protocol.
- The overall performance and efficiency.
- The overall extensibility of my design choices.
Any feedback touching on best practices, superior library or library use, or general readability of the program is welcomed. I haven't handled some of the exceptions yet, that is intentional while I was creating this.
For anyone interested this is the Github repository.