Migrate everything on Gradle
Also added tasks to run the server & client separately
This commit is contained in:
21
ChatApp/app/src/main/java/ChatApp.java
Normal file
21
ChatApp/app/src/main/java/ChatApp.java
Normal file
@@ -0,0 +1,21 @@
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import client.ClientConsole;
|
||||
import server.Server;
|
||||
|
||||
public class ChatApp {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Server server = new Server(6665);
|
||||
ClientConsole client = new ClientConsole(new InetSocketAddress("localhost", 6665));
|
||||
|
||||
client.getClientInterface().SendCreateRoom("101");
|
||||
|
||||
client.joinThread();
|
||||
|
||||
System.out.println("Stopping server ...");
|
||||
|
||||
server.close();
|
||||
|
||||
System.out.println("Done !");
|
||||
}
|
||||
}
|
||||
15
ChatApp/app/src/main/java/ChatAppClient.java
Normal file
15
ChatApp/app/src/main/java/ChatAppClient.java
Normal file
@@ -0,0 +1,15 @@
|
||||
import client.ClientConsole;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class ChatAppClient {
|
||||
public static void main(String[] args) {
|
||||
ClientConsole console = new ClientConsole(new InetSocketAddress("localhost", 6665));
|
||||
try {
|
||||
console.joinThread();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("End !");
|
||||
}
|
||||
}
|
||||
53
ChatApp/app/src/main/java/client/Client.java
Normal file
53
ChatApp/app/src/main/java/client/Client.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package client;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
import network.protocol.packets.*;
|
||||
|
||||
public class Client {
|
||||
|
||||
private final ClientConnexion connexion;
|
||||
private final ClientListener callback;
|
||||
|
||||
public Client(InetSocketAddress serverAddress, ClientListener callback, String pseudo) throws SocketException {
|
||||
this.connexion = new ClientConnexion(new DatagramSocket(), serverAddress, callback);
|
||||
this.callback = callback;
|
||||
login(pseudo);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.connexion.sendPacket(new DisconnectPacket("Leaving"));
|
||||
this.connexion.close();
|
||||
this.callback.handleDisconnect();
|
||||
}
|
||||
|
||||
private void login(String pseudo) {
|
||||
this.connexion.sendPacket(new LoginPacket(pseudo));
|
||||
}
|
||||
|
||||
public void SendChatMessage(String message) {
|
||||
this.connexion.sendPacket(new SendChatMessagePacket(message));
|
||||
}
|
||||
|
||||
public void SendCreateRoom(String roomName) {
|
||||
this.connexion.sendPacket(new CreateRoomPacket(roomName));
|
||||
}
|
||||
|
||||
public void SendJoinRoom(String roomName) {
|
||||
this.connexion.sendPacket(new JoinRoomPacket(roomName));
|
||||
}
|
||||
|
||||
public void SendLeaveRoom() {
|
||||
this.connexion.sendPacket(new LeaveRoomPacket());
|
||||
}
|
||||
|
||||
public void RequestRoomList() {
|
||||
this.connexion.sendPacket(new RequestRoomListPacket());
|
||||
}
|
||||
|
||||
public void RequestActualRoom() {
|
||||
this.connexion.sendPacket(new RequestActualRoomPacket());
|
||||
}
|
||||
}
|
||||
139
ChatApp/app/src/main/java/client/ClientConnexion.java
Normal file
139
ChatApp/app/src/main/java/client/ClientConnexion.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import network.PacketHandler;
|
||||
import network.SocketReader;
|
||||
import network.SocketWriter;
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
import network.protocol.packets.*;
|
||||
|
||||
public class ClientConnexion implements PacketVisitor, PacketHandler {
|
||||
|
||||
private final InetSocketAddress serverAddress;
|
||||
private final SocketWriter writer;
|
||||
private final SocketReader reader;
|
||||
private final ClientListener callback;
|
||||
private volatile boolean connected = false;
|
||||
|
||||
public ClientConnexion(DatagramSocket socket, InetSocketAddress serverAddress, ClientListener callback) {
|
||||
this.serverAddress = serverAddress;
|
||||
this.writer = new SocketWriter(socket);
|
||||
this.reader = new SocketReader(socket, this);
|
||||
this.callback = callback;
|
||||
spamHandshake();
|
||||
}
|
||||
|
||||
private void spamHandshake() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
sendPacket(new HandshakePacket());
|
||||
}
|
||||
new Thread(this::waitForHandshake).start();
|
||||
}
|
||||
|
||||
private void waitForHandshake() {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(!connected) {
|
||||
System.out.println("The server did not respond !");
|
||||
this.close();
|
||||
this.callback.handleConnexionError();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.reader.stop();
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet) {
|
||||
try {
|
||||
this.writer.sendPacket(packet, serverAddress);
|
||||
} catch (IOException e) {
|
||||
this.close();
|
||||
this.callback.handleConnexionError();
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePacket(Packet packet, InetSocketAddress address) {
|
||||
// we assume that the packet comes from the server
|
||||
visit(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ChatMessagePacket packet) {
|
||||
this.callback.handleChatMessage(packet.getTime(), packet.getChatter(), packet.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RoomListPacket packet) {
|
||||
this.callback.handleRoomList(packet.getRoomNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ServerResponsePacket packet) {
|
||||
this.callback.handleServerResponse(packet.getResponse());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestActualRoomPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ActualRoomPacket packet) {
|
||||
this.callback.handleActualRoom(packet.getRoomName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(DisconnectPacket packet) {
|
||||
this.close();
|
||||
this.connected = false;
|
||||
this.callback.handleDisconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(HandshakePacket packet) {
|
||||
if (!connected)
|
||||
this.callback.handleConnect();
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(CreateRoomPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(JoinRoomPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LeaveRoomPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LoginPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestRoomListPacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(SendChatMessagePacket packet) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
}
|
||||
197
ChatApp/app/src/main/java/client/ClientConsole.java
Normal file
197
ChatApp/app/src/main/java/client/ClientConsole.java
Normal file
@@ -0,0 +1,197 @@
|
||||
package client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import network.protocol.ANSIColor;
|
||||
import network.protocol.packets.ServerResponsePacket;
|
||||
import network.protocol.packets.ServerResponsePacket.Response;
|
||||
|
||||
public class ClientConsole implements ClientListener {
|
||||
|
||||
private Client client;
|
||||
private final Thread inputThread;
|
||||
private final Scanner scanner;
|
||||
private volatile boolean connected = false;
|
||||
|
||||
public ClientConsole(InetSocketAddress address) {
|
||||
this.inputThread = new Thread(this::inputLoop);
|
||||
this.scanner = new Scanner(System.in);
|
||||
String pseudo = inputPseudo();
|
||||
try {
|
||||
this.client = new Client(address, this, pseudo);
|
||||
this.inputThread.start();
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String inputPseudo() {
|
||||
System.out.println("Enter your pseudo:");
|
||||
String pseudo = "chatter";
|
||||
pseudo = this.scanner.nextLine();
|
||||
return pseudo;
|
||||
}
|
||||
|
||||
public Client getClientInterface() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
private void inputLoop() {
|
||||
// waiting to be connected
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
if (!connected)
|
||||
return;
|
||||
}
|
||||
|
||||
// resets the interrupt
|
||||
Thread.interrupted();
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
String message = this.scanner.nextLine();
|
||||
if (Thread.interrupted())
|
||||
break;
|
||||
visitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void joinThread() throws InterruptedException {
|
||||
this.inputThread.join();
|
||||
}
|
||||
|
||||
private void printHelp(Map<List<String>, Consumer<String>> commands) {
|
||||
System.out.println("Available commands:");
|
||||
for (var entry : commands.entrySet()) {
|
||||
List<String> commandNames = entry.getKey();
|
||||
System.out.println("\t" + commandNames.get(0) + (commandNames.size() == 2 ? " or " + commandNames.get(1) : ""));
|
||||
}
|
||||
// the clearLine eats the last line if a new line is not skipped
|
||||
System.out.println("\n");
|
||||
}
|
||||
|
||||
private void visitMessage(String message) {
|
||||
if (!message.startsWith("/")) {
|
||||
this.client.SendChatMessage(message);
|
||||
clearLine();
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<List<String>, Consumer<String>> commands = Map.of(
|
||||
List.of("/createRoom <roomName>", "/create <roomName>"), (args) -> this.client.SendCreateRoom(args),
|
||||
List.of("/listRooms", "/list"), (args) -> this.client.RequestRoomList(),
|
||||
List.of("/joinRoom <roomName>", "/join <roomName>"), (args) -> this.client.SendJoinRoom(args),
|
||||
List.of("/leaveRoom", "/leave"), (args) -> this.client.SendLeaveRoom(),
|
||||
List.of("/actualRoom", "/room"), (args) -> this.client.RequestActualRoom(),
|
||||
List.of("/goodbye", "/bye"), (args) -> this.client.close(),
|
||||
List.of("/help"), (args) -> {});
|
||||
|
||||
boolean commandFound = false;
|
||||
|
||||
for (var entry : commands.entrySet()) {
|
||||
List<String> commandNames = entry.getKey();
|
||||
Consumer<String> runnable = entry.getValue();
|
||||
for (String cmd : commandNames) {
|
||||
String[] cmdParts = cmd.split(" ");
|
||||
if (cmdParts.length > 1) {
|
||||
// the command expects arguments
|
||||
if (message.startsWith(cmdParts[0] + " ")) {
|
||||
runnable.accept(message.substring(cmdParts[0].length() + 1).trim());
|
||||
commandFound = true;
|
||||
}
|
||||
} else {
|
||||
// the command does not expect arguments
|
||||
if (message.equals(cmd)) {
|
||||
runnable.accept(message.substring(cmd.length()).trim());
|
||||
commandFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (commandFound)
|
||||
break;
|
||||
}
|
||||
|
||||
if (commandFound) {
|
||||
if (message.equals("/help"))
|
||||
printHelp(commands);
|
||||
clearLine();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(ANSIColor.formatString("&rUnknown command&n"));
|
||||
printHelp(commands);
|
||||
clearLine();
|
||||
}
|
||||
|
||||
private void clearLine() {
|
||||
System.out.print("\033[1A\r\033[2K"); // weird sequence to clear the line (but it works !)
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
this.inputThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect() {
|
||||
System.out.println("Disconnected !");
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConnexionError() {
|
||||
System.out.println("An error occured during the connexion !");
|
||||
this.connected = false;
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleChatMessage(Instant time, String chatter, String content) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String strTime = time.toString();
|
||||
sb.append("&y[")
|
||||
.append(strTime, 11, 19) // We only take the HH:MM:SS part
|
||||
.append("]&n")
|
||||
.append(" ")
|
||||
.append(chatter)
|
||||
.append(" : ")
|
||||
.append(content).append("&n"); // make the color back to normal at the end of every message
|
||||
System.out.println(ANSIColor.formatString(sb.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRoomList(List<String> roomNames) {
|
||||
System.out.println(roomNames.isEmpty()
|
||||
? "No rooms available"
|
||||
: "Rooms : \n\t" + String.join("\n\t", roomNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleActualRoom(String roomName) {
|
||||
System.out.println(roomName != null ? ANSIColor.formatString("You are now in room &b" + roomName + "&n") : "You are not in a room");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServerResponse(Response response) {
|
||||
if (response == ServerResponsePacket.Response.MessageSent
|
||||
|| response == ServerResponsePacket.Response.MessageNotSent) {
|
||||
return;
|
||||
}
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConnect() {
|
||||
System.out.println("Connected to server !");
|
||||
this.connected = true;
|
||||
this.inputThread.interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
18
ChatApp/app/src/main/java/client/ClientListener.java
Normal file
18
ChatApp/app/src/main/java/client/ClientListener.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package client;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import network.protocol.packets.ServerResponsePacket;
|
||||
|
||||
public interface ClientListener {
|
||||
|
||||
void handleDisconnect();
|
||||
void handleConnexionError();
|
||||
void handleConnect();
|
||||
void handleChatMessage(Instant time, String chatter, String content);
|
||||
void handleRoomList(List<String> roomNames);
|
||||
void handleServerResponse(ServerResponsePacket.Response response);
|
||||
void handleActualRoom(String roomName);
|
||||
|
||||
}
|
||||
9
ChatApp/app/src/main/java/network/PacketHandler.java
Normal file
9
ChatApp/app/src/main/java/network/PacketHandler.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import network.protocol.Packet;
|
||||
|
||||
public interface PacketHandler {
|
||||
void handlePacket(Packet packet, InetSocketAddress address);
|
||||
}
|
||||
49
ChatApp/app/src/main/java/network/SocketReader.java
Normal file
49
ChatApp/app/src/main/java/network/SocketReader.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package network;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import network.protocol.Packet;
|
||||
|
||||
public class SocketReader {
|
||||
|
||||
private final DatagramSocket socket;
|
||||
private final Thread readThread;
|
||||
private final PacketHandler handler;
|
||||
|
||||
public SocketReader(DatagramSocket socket, PacketHandler handler) {
|
||||
this.socket = socket;
|
||||
this.handler = handler;
|
||||
this.readThread = new Thread(this::readLoop);
|
||||
this.readThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.readThread.interrupt();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
private void readLoop() {
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
byte[] buffer = new byte[65535];
|
||||
DatagramPacket dataPacket = new DatagramPacket(buffer, buffer.length);
|
||||
socket.receive(dataPacket);
|
||||
|
||||
InetSocketAddress address = new InetSocketAddress(dataPacket.getAddress(), dataPacket.getPort());
|
||||
|
||||
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(dataPacket.getData()));
|
||||
Packet packet = (Packet) ois.readObject();
|
||||
|
||||
this.handler.handlePacket(packet, address);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
ChatApp/app/src/main/java/network/SocketWriter.java
Normal file
30
ChatApp/app/src/main/java/network/SocketWriter.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package network;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import network.protocol.Packet;
|
||||
|
||||
public class SocketWriter {
|
||||
|
||||
private final DatagramSocket socket;
|
||||
|
||||
public SocketWriter(DatagramSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet, InetSocketAddress address) throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(stream);
|
||||
oos.writeObject(packet);
|
||||
oos.flush();
|
||||
byte[] data = stream.toByteArray();
|
||||
DatagramPacket dataPacket = new DatagramPacket(data, data.length, address.getAddress(),
|
||||
address.getPort());
|
||||
this.socket.send(dataPacket);
|
||||
}
|
||||
}
|
||||
24
ChatApp/app/src/main/java/network/protocol/ANSIColor.java
Normal file
24
ChatApp/app/src/main/java/network/protocol/ANSIColor.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package network.protocol;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ANSIColor {
|
||||
public static final String RESET = "\u001B[0m";
|
||||
public static final String BLACK = "\u001B[30m";
|
||||
public static final String RED = "\u001B[31m";
|
||||
public static final String GREEN = "\u001B[32m";
|
||||
public static final String BLUE = "\u001B[34m";
|
||||
public static final String GREY = "\u001B[37m";
|
||||
|
||||
public static String formatString(String message){
|
||||
return message.replace("&r", RED)
|
||||
.replace("&g", GREEN)
|
||||
.replace("&b", BLUE)
|
||||
.replace("&y", GREY)
|
||||
.replace("&n", RESET);
|
||||
}
|
||||
|
||||
public static String tag(String message, String chatter){
|
||||
return message.replaceAll("(@" + Pattern.quote(chatter) + ")", "\u001B[44;30m$1\u001B[49;39m" );
|
||||
}
|
||||
}
|
||||
9
ChatApp/app/src/main/java/network/protocol/Packet.java
Normal file
9
ChatApp/app/src/main/java/network/protocol/Packet.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package network.protocol;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class Packet implements Serializable {
|
||||
|
||||
public abstract void accept(PacketVisitor packetVisitor);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package network.protocol;
|
||||
|
||||
import network.protocol.packets.*;
|
||||
|
||||
public interface PacketVisitor {
|
||||
|
||||
default void visit(Packet packet) {
|
||||
packet.accept(this);
|
||||
}
|
||||
|
||||
void visitPacket(ChatMessagePacket packet);
|
||||
void visitPacket(CreateRoomPacket packet);
|
||||
void visitPacket(DisconnectPacket packet);
|
||||
void visitPacket(HandshakePacket packet);
|
||||
void visitPacket(JoinRoomPacket packet);
|
||||
void visitPacket(LeaveRoomPacket packet);
|
||||
void visitPacket(LoginPacket packet);
|
||||
void visitPacket(RequestRoomListPacket packet);
|
||||
void visitPacket(RoomListPacket packet);
|
||||
void visitPacket(SendChatMessagePacket packet);
|
||||
void visitPacket(ServerResponsePacket packet);
|
||||
void visitPacket(RequestActualRoomPacket packet);
|
||||
void visitPacket(ActualRoomPacket packet);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class ActualRoomPacket extends Packet {
|
||||
private final String roomName;
|
||||
|
||||
public ActualRoomPacket(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class ChatMessagePacket extends Packet {
|
||||
|
||||
private final Instant time;
|
||||
private final String chatter;
|
||||
private final String content;
|
||||
|
||||
public ChatMessagePacket(Instant time, String chatter, String content) {
|
||||
this.time = time;
|
||||
this.chatter = chatter;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public String getChatter() {
|
||||
return chatter;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class CreateRoomPacket extends Packet {
|
||||
|
||||
private final String roomName;
|
||||
|
||||
public CreateRoomPacket(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class DisconnectPacket extends Packet {
|
||||
|
||||
private final String reason;
|
||||
|
||||
public DisconnectPacket(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class HandshakePacket extends Packet {
|
||||
public HandshakePacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class JoinRoomPacket extends Packet {
|
||||
|
||||
private final String roomName;
|
||||
|
||||
public JoinRoomPacket(String roomName) {
|
||||
this.roomName = roomName;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return roomName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class LeaveRoomPacket extends Packet {
|
||||
|
||||
public LeaveRoomPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class LoginPacket extends Packet {
|
||||
|
||||
private final String pseudo;
|
||||
|
||||
public LoginPacket(String pseudo) {
|
||||
this.pseudo = pseudo;
|
||||
}
|
||||
|
||||
public String getPseudo() {
|
||||
return pseudo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class RequestActualRoomPacket extends Packet {
|
||||
public RequestActualRoomPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class RequestRoomListPacket extends Packet {
|
||||
|
||||
public RequestRoomListPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class RoomListPacket extends Packet {
|
||||
|
||||
private final List<String> roomNames;
|
||||
|
||||
public RoomListPacket(List<String> roomNames) {
|
||||
this.roomNames = roomNames;
|
||||
}
|
||||
|
||||
public List<String> getRoomNames() {
|
||||
return roomNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class SendChatMessagePacket extends Packet {
|
||||
|
||||
private final String content;
|
||||
|
||||
public SendChatMessagePacket(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package network.protocol.packets;
|
||||
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
|
||||
public class ServerResponsePacket extends Packet {
|
||||
|
||||
public static enum Response {
|
||||
AuthSuccess, AuthError, RoomCreated, RoomNotCreated, RoomJoined, RoomNotJoined, NotInRoom, RoomLeft, RoomNotLeft, MessageSent, MessageNotSent;
|
||||
};
|
||||
|
||||
private final Response response;
|
||||
|
||||
public ServerResponsePacket(Response response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public Response getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PacketVisitor packetVisitor) {
|
||||
packetVisitor.visitPacket(this);
|
||||
}
|
||||
|
||||
}
|
||||
152
ChatApp/app/src/main/java/server/Server.java
Normal file
152
ChatApp/app/src/main/java/server/Server.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package server;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import network.PacketHandler;
|
||||
import network.SocketReader;
|
||||
import network.protocol.ANSIColor;
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.packets.ChatMessagePacket;
|
||||
import network.protocol.packets.SendChatMessagePacket;
|
||||
|
||||
public class Server implements PacketHandler {
|
||||
|
||||
private final DatagramSocket serverSocket;
|
||||
private final Map<InetSocketAddress, ServerConnexion> connexions;
|
||||
private final Map<InetSocketAddress, Instant> connexionTimes;
|
||||
private final SocketReader reader;
|
||||
private final Map<String, ArrayList<ServerConnexion>> rooms;
|
||||
|
||||
public Server(int port) throws SocketException {
|
||||
this.serverSocket = new DatagramSocket(port);
|
||||
this.connexions = new HashMap<>();
|
||||
this.connexionTimes = new HashMap<>();
|
||||
this.reader = new SocketReader(serverSocket, this);
|
||||
this.rooms = new HashMap<>();
|
||||
}
|
||||
|
||||
public ArrayList<String> getRoomNames() {
|
||||
return rooms.keySet().stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
|
||||
}
|
||||
|
||||
public boolean isInRoom(ServerConnexion connexion) {
|
||||
return getRoomName(connexion) != null;
|
||||
}
|
||||
|
||||
public String getRoomName(ServerConnexion connexion) {
|
||||
for (Map.Entry<String, ArrayList<ServerConnexion>> entry : rooms.entrySet()) {
|
||||
if (entry.getValue().contains(connexion)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean createRoom(String roomName, ServerConnexion connexion) {
|
||||
if (rooms.containsKey(roomName)) {
|
||||
return false;
|
||||
}
|
||||
rooms.put(roomName, new ArrayList<>());
|
||||
leaveRoom(connexion); // Leave the current room (auto handle if not in a room)
|
||||
rooms.get(roomName).add(connexion); // Add the creator to the room
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean leaveRoom(ServerConnexion connexion) {
|
||||
String roomName = getRoomName(connexion);
|
||||
if (roomName != null) {
|
||||
rooms.get(roomName).remove(connexion);
|
||||
// Remove the room if it is empty
|
||||
if (rooms.get(roomName).isEmpty()) {
|
||||
rooms.remove(roomName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean joinRoom(String roomName, ServerConnexion connexion) {
|
||||
leaveRoom(connexion); // Leave the current room (auto handle if not in a room)
|
||||
if (rooms.containsKey(roomName) && !rooms.get(roomName).contains(connexion)) {
|
||||
rooms.get(roomName).add(connexion);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean sendToRoom(String roomName, ChatMessagePacket packet) {
|
||||
if (roomName != null && rooms.containsKey(roomName)) {
|
||||
rooms.get(roomName).forEach(con -> con.sendPacket(
|
||||
new ChatMessagePacket(packet.getTime(), packet.getChatter(), ANSIColor.tag(packet.getContent(), con.getChatterName()))
|
||||
));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean sendToRoom(ServerConnexion connexion, ChatMessagePacket packet) {
|
||||
String roomName = getRoomName(connexion);
|
||||
return sendToRoom(roomName, packet);
|
||||
}
|
||||
|
||||
public boolean sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) {
|
||||
return sendToRoom(connexion,
|
||||
new ChatMessagePacket(Instant.now(), connexion.getChatterName(), packet.getContent()));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.reader.stop();
|
||||
}
|
||||
|
||||
public boolean hasChatterName(String pseudo) {
|
||||
return this.connexions.values().stream()
|
||||
.anyMatch(connexion -> pseudo.equals(connexion.getChatterName()));
|
||||
}
|
||||
|
||||
void removeConnexion(ServerConnexion connexion) {
|
||||
for (var it = this.connexions.entrySet().iterator(); it.hasNext();) {
|
||||
var entry = it.next();
|
||||
if (entry.getValue() == connexion) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (var entry : this.rooms.entrySet()) {
|
||||
if (entry.getValue().remove(connexion))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePacket(Packet packet, InetSocketAddress address) {
|
||||
if (!connexions.containsKey(address)) {
|
||||
this.connexions.put(address, new ServerConnexion(this, serverSocket, address));
|
||||
}
|
||||
this.connexions.get(address).visit(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid the server to spam the chat
|
||||
* @param address the address of the connexion
|
||||
* @return true if the connexion is the first received or older than 5 seconds
|
||||
*/
|
||||
public boolean handleConnexionTime(InetSocketAddress address) {
|
||||
if (!connexionTimes.containsKey(address)) {
|
||||
connexionTimes.put(address, Instant.now());
|
||||
return true;
|
||||
}
|
||||
Instant lastConnexion = connexionTimes.get(address);
|
||||
if (Instant.now().isAfter(lastConnexion.plusSeconds(5))) {
|
||||
connexionTimes.put(address, Instant.now());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
152
ChatApp/app/src/main/java/server/ServerConnexion.java
Normal file
152
ChatApp/app/src/main/java/server/ServerConnexion.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Instant;
|
||||
|
||||
import network.SocketWriter;
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
import network.protocol.packets.*;
|
||||
import network.protocol.packets.ServerResponsePacket.Response;
|
||||
|
||||
public class ServerConnexion implements PacketVisitor {
|
||||
|
||||
private final Server server;
|
||||
private final InetSocketAddress clientAddress;
|
||||
private final SocketWriter writer;
|
||||
private String chatterName;
|
||||
|
||||
public ServerConnexion(Server server, DatagramSocket socket, InetSocketAddress clientAddress) {
|
||||
this.clientAddress = clientAddress;
|
||||
this.server = server;
|
||||
this.writer = new SocketWriter(socket);
|
||||
}
|
||||
|
||||
public String getChatterName() {
|
||||
return this.chatterName;
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet) {
|
||||
try {
|
||||
this.writer.sendPacket(packet, clientAddress);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.server.removeConnexion(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(CreateRoomPacket packet) {
|
||||
boolean created = server.createRoom(packet.getRoomName(), this);
|
||||
sendPacket(new ServerResponsePacket(created ? Response.RoomCreated : Response.RoomNotCreated));
|
||||
if (created)
|
||||
onRoomJoin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(JoinRoomPacket packet) {
|
||||
if(server.getRoomName(this) != null) {
|
||||
server.leaveRoom(this);
|
||||
return;
|
||||
}
|
||||
boolean joined = server.joinRoom(packet.getRoomName(), this);
|
||||
sendPacket(new ServerResponsePacket(joined ? Response.RoomJoined : Response.RoomNotJoined));
|
||||
if (joined)
|
||||
onRoomJoin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LeaveRoomPacket packet) {
|
||||
String roomName = this.server.getRoomName(this);
|
||||
boolean left = server.leaveRoom(this);
|
||||
sendPacket(new ServerResponsePacket(left ? Response.RoomLeft : Response.RoomNotLeft));
|
||||
if (left)
|
||||
onRoomLeave(roomName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LoginPacket packet) {
|
||||
if (packet.getPseudo().isEmpty() || server.hasChatterName(packet.getPseudo())) {
|
||||
sendPacket(new ServerResponsePacket(Response.AuthError));
|
||||
return;
|
||||
}
|
||||
|
||||
this.chatterName = packet.getPseudo();
|
||||
sendPacket(new RoomListPacket(server.getRoomNames()));
|
||||
System.out.println("[Server] Chatter " + packet.getPseudo() + " connected !");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestRoomListPacket packet) {
|
||||
sendPacket(new RoomListPacket(server.getRoomNames()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(SendChatMessagePacket packet) {
|
||||
boolean messageSent = server.sendToRoom(this, packet);
|
||||
sendPacket(new ServerResponsePacket(messageSent ? Response.MessageSent : Response.MessageNotSent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(DisconnectPacket packet) {
|
||||
this.onDisconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(HandshakePacket packet) {
|
||||
if(this.server.handleConnexionTime(this.clientAddress)) {
|
||||
System.out.println("[Server] Handshake received from " + clientAddress);
|
||||
}
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
if (this.server.isInRoom(this)) {
|
||||
this.onRoomLeave(this.server.getRoomName(this));
|
||||
}
|
||||
this.server.removeConnexion(this);
|
||||
System.out.println("[Server] Chatter " + chatterName + " disconnected !");
|
||||
}
|
||||
|
||||
private void onRoomJoin() {
|
||||
String joinMessage = "Chatter " + this.chatterName + " joined the room !";
|
||||
this.server.sendToRoom(this, new ChatMessagePacket(Instant.now(), "", joinMessage));
|
||||
}
|
||||
|
||||
private void onRoomLeave(String roomName) {
|
||||
String joinMessage = "Chatter " + this.chatterName + " left the room !";
|
||||
this.server.sendToRoom(roomName, new ChatMessagePacket(Instant.now(), "", joinMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RoomListPacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ChatMessagePacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ServerResponsePacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestActualRoomPacket packet) {
|
||||
sendPacket(new ActualRoomPacket(server.getRoomName(this)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ActualRoomPacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user