Compare commits

..

1 Commits

Author SHA1 Message Date
Clément
6d3bca136a Handshake to test if the server exists
simplification of some commands
2025-03-01 13:18:14 +01:00
13 changed files with 255 additions and 337 deletions

View File

@@ -29,4 +29,11 @@ You will also be able to create a new room.
- /listRooms
- /joinRoom *roomName*
- /leaveRoom
- /help
- /help
> [!NOTE]
> There are some aliases for the commands:
> - /create
> - /list
> - /join
> - /leave

View File

@@ -1,21 +1,24 @@
import java.net.InetSocketAddress;
import java.util.Scanner;
import client.ClientConsole;
import client.Client;
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));
int port = 6665;
Server server = new Server(port);
Client client = new Client(new InetSocketAddress("localhost", port));
client.getClientInterface().SendCreateRoom("101");
client.SendCreateRoom("101");
client.joinThread();
System.out.println("Stopping server ...");
server.close();
System.out.println("Done !");
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.nextLine();
System.out.print("\033[1A");
System.out.print("\r\033[2K");
System.out.flush();
client.visitMessage(message);
}
}
}

View File

@@ -1,55 +1,142 @@
package client;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Objects;
import java.util.Scanner;
import network.protocol.packets.CreateRoomPacket;
import network.protocol.packets.DisconnectPacket;
import network.protocol.packets.JoinRoomPacket;
import network.protocol.packets.LeaveRoomPacket;
import network.protocol.packets.LoginPacket;
import network.protocol.packets.RequestRoomListPacket;
import network.protocol.packets.SendChatMessagePacket;
import network.protocol.ANSIColor;
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;
public static void main(String[] args) {
String host = "localhost";
int port = 6665;
try {
Client client = new Client(new InetSocketAddress(host, port));
Scanner scanner = new Scanner(System.in);
while(true) {
String message = scanner.nextLine();
client.visitMessage(message);
}
} catch (SocketException e) {
e.printStackTrace();
}
}
public Client(InetSocketAddress serverAddress) throws SocketException {
this.connexion = new ClientConnexion(new DatagramSocket(), serverAddress);
int tries = 0;
try {
connexion.sendPacket(new HandshakePacket());
} catch (IOException e) {
e.printStackTrace();
}
while(!connexion.connected) {
try {
Thread.sleep(100);
if(tries++ > 5) {
System.out.println("Server is not responding");
System.exit(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Scanner scanner = new Scanner(System.in);
System.out.println("Enter your pseudo:");
String pseudo = scanner.nextLine();
login(pseudo);
}
public void close() {
this.connexion.sendPacket(new DisconnectPacket("Leaving"));
this.connexion.close();
this.callback.handleDisconnect();
public void visitMessage(String message){
try {
if(message.startsWith("/")){
if(message.startsWith("/createRoom ")) {
String roomName = message.substring(12).trim();
SendCreateRoom(roomName);
} else if(message.startsWith("/create ")){
String roomName = message.substring(8).trim();
SendCreateRoom(roomName);
} else if(message.equals("/listRooms") || message.equals("/list")) {
RequestRoomList();
} else if(message.startsWith("/joinRoom ")) {
String roomName = message.substring(10).trim();
SendJoinRoom(roomName);
} else if(message.startsWith("/join ")){
String roomName = message.substring(6).trim();
SendJoinRoom(roomName);
} else if(message.equals("/leaveRoom") || message.equals("/leave")) {
SendLeaveRoom();
} else if(message.equals("/help")) {
System.out.println("Available commands:");
System.out.println("\t/createRoom <roomName>");
System.out.println("\t/listRooms");
System.out.println("\t/joinRoom <roomName>");
System.out.println("\t/leaveRoom");
System.out.println("\t/help");
}
else {
System.out.println(ANSIColor.formatString("&rUnknown command&n"));
}
} else {
SendChatMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void login(String pseudo) {
this.connexion.sendPacket(new LoginPacket(pseudo));
try {
this.connexion.sendPacket(new LoginPacket(pseudo));
} catch (IOException e) {
e.printStackTrace();
}
}
public void SendChatMessage(String message) {
this.connexion.sendPacket(new SendChatMessagePacket(message));
try {
this.connexion.sendPacket(new SendChatMessagePacket(message));
} catch (IOException e) {
e.printStackTrace();
}
}
public void SendCreateRoom(String roomName) {
this.connexion.sendPacket(new CreateRoomPacket(roomName));
try {
this.connexion.sendPacket(new CreateRoomPacket(roomName));
} catch (Exception e) {
e.printStackTrace();
}
}
public void SendJoinRoom(String roomName) {
this.connexion.sendPacket(new JoinRoomPacket(roomName));
try {
this.connexion.sendPacket(new JoinRoomPacket(roomName));
} catch (Exception e) {
e.printStackTrace();
}
}
public void SendLeaveRoom() {
this.connexion.sendPacket(new LeaveRoomPacket());
try {
this.connexion.sendPacket(new LeaveRoomPacket());
} catch (Exception e) {
e.printStackTrace();
}
}
public void RequestRoomList() {
this.connexion.sendPacket(new RequestRoomListPacket());
try {
this.connexion.sendPacket(new RequestRoomListPacket());
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -7,45 +7,30 @@ import java.net.InetSocketAddress;
import network.PacketHandler;
import network.SocketReader;
import network.SocketWriter;
import network.protocol.ANSIColor;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
import network.protocol.packets.ChatMessagePacket;
import network.protocol.packets.CreateRoomPacket;
import network.protocol.packets.DisconnectPacket;
import network.protocol.packets.JoinRoomPacket;
import network.protocol.packets.LeaveRoomPacket;
import network.protocol.packets.LoginPacket;
import network.protocol.packets.RequestRoomListPacket;
import network.protocol.packets.RoomListPacket;
import network.protocol.packets.SendChatMessagePacket;
import network.protocol.packets.ServerResponsePacket;
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;
protected boolean connected = false;
public ClientConnexion(DatagramSocket socket, InetSocketAddress serverAddress, ClientListener callback) {
public ClientConnexion(DatagramSocket socket, InetSocketAddress serverAddress) {
this.serverAddress = serverAddress;
this.writer = new SocketWriter(socket);
this.reader = new SocketReader(socket, this);
this.callback = callback;
}
public void close() {
this.reader.stop();
}
public void sendPacket(Packet packet) {
try {
this.writer.sendPacket(packet, serverAddress);
} catch (IOException e) {
this.close();
this.callback.handleDisconnect();
e.printStackTrace();
}
public void sendPacket(Packet packet) throws IOException {
this.writer.sendPacket(packet, serverAddress);
}
@Override
@@ -56,23 +41,16 @@ public class ClientConnexion implements PacketVisitor, PacketHandler{
@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(DisconnectPacket packet) {
this.close();
this.callback.handleDisconnect();
StringBuilder sb = new StringBuilder();
String time = packet.getTime().toString();
sb.append("&y[")
.append(time, 11, 19) // We only take the HH:MM:SS part
.append("]&n")
.append(" ")
.append(packet.getChatter())
.append(" : ")
.append(packet.getContent()).append("&n"); // make the color back to normal at the end of every message
System.out.println(ANSIColor.formatString(sb.toString()));
}
@Override
@@ -100,9 +78,29 @@ public class ClientConnexion implements PacketVisitor, PacketHandler{
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
}
@Override
public void visitPacket(RoomListPacket packet) {
System.out.println("Rooms :");
for (String room : packet.getRoomNames()) {
System.out.println("\t" + room);
}
}
@Override
public void visitPacket(SendChatMessagePacket packet) {
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
}
@Override
public void visitPacket(ServerResponsePacket packet) {
if(packet.getResponse() == ServerResponsePacket.Response.MessageSent || packet.getResponse() == ServerResponsePacket.Response.MessageNotSent) {
return;
}
System.out.println(packet.getResponse());
}
@Override
public void visitPacket(HandshakePacket packet) {
connected = true;
}
}

View File

@@ -1,128 +0,0 @@
package client;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.time.Instant;
import java.util.List;
import java.util.Scanner;
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;
public static void main(String[] args) {
new ClientConsole(new InetSocketAddress("localhost", 6665));
}
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 = this.scanner.nextLine();
return pseudo;
}
public Client getClientInterface() {
return this.client;
}
private void inputLoop() {
while (!Thread.interrupted()) {
String message = scanner.nextLine();
visitMessage(message);
}
this.scanner.close();
}
public void joinThread() throws InterruptedException {
this.inputThread.join();
}
private void visitMessage(String message) {
try {
if (message.startsWith("/")) {
if (message.startsWith("/createRoom")) {
String roomName = message.substring(12).trim();
this.client.SendCreateRoom(roomName);
} else if (message.startsWith("/listRooms")) {
this.client.RequestRoomList();
} else if (message.startsWith("/joinRoom")) {
String roomName = message.substring(10).trim();
this.client.SendJoinRoom(roomName);
} else if (message.startsWith("/leaveRoom")) {
this.client.SendLeaveRoom();
} else if (message.startsWith("/bye")) {
this.client.close();
} else if (message.startsWith("/help")) {
System.out.println("Available commands:");
System.out.println("\t/bye");
System.out.println("\t/createRoom <roomName>");
System.out.println("\t/listRooms");
System.out.println("\t/joinRoom <roomName>");
System.out.println("\t/leaveRoom");
System.out.println("\t/help");
} else {
System.out.println("Unknown command");
}
} else {
this.client.SendChatMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void handleDisconnect() {
System.out.println("Disconnected !");
this.inputThread.interrupt();
}
@Override
public void handleChatMessage(Instant time, String chatter, String content) {
StringBuilder sb = new StringBuilder();
String strTime = time.toString();
sb.append("&y[");
sb.append(strTime, 11, 19); // We only take the HH:MM:SS part
sb.append("]&n");
sb.append(" ");
sb.append(chatter);
sb.append(" : ");
sb.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("Rooms :");
for (String room : roomNames) {
System.out.println("\t" + room);
}
}
@Override
public void handleServerResponse(Response response) {
if (response == ServerResponsePacket.Response.MessageSent
|| response == ServerResponsePacket.Response.MessageNotSent) {
return;
}
System.out.println(response);
}
}

View File

@@ -1,15 +0,0 @@
package client;
import java.time.Instant;
import java.util.List;
import network.protocol.packets.ServerResponsePacket;
public interface ClientListener {
void handleDisconnect();
void handleChatMessage(Instant time, String chatter, String content);
void handleRoomList(List<String> roomNames);
void handleServerResponse(ServerResponsePacket.Response response);
}

View File

@@ -24,7 +24,6 @@ public class SocketReader {
public void stop() {
this.readThread.interrupt();
socket.close();
}
private void readLoop() {
@@ -33,9 +32,9 @@ public class SocketReader {
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();

View File

@@ -1,15 +1,6 @@
package network.protocol;
import network.protocol.packets.ChatMessagePacket;
import network.protocol.packets.CreateRoomPacket;
import network.protocol.packets.DisconnectPacket;
import network.protocol.packets.JoinRoomPacket;
import network.protocol.packets.LeaveRoomPacket;
import network.protocol.packets.LoginPacket;
import network.protocol.packets.RequestRoomListPacket;
import network.protocol.packets.RoomListPacket;
import network.protocol.packets.SendChatMessagePacket;
import network.protocol.packets.ServerResponsePacket;
import network.protocol.packets.*;
public interface PacketVisitor {
@@ -19,7 +10,6 @@ public interface PacketVisitor {
void visitPacket(ChatMessagePacket packet);
void visitPacket(CreateRoomPacket packet);
void visitPacket(DisconnectPacket packet);
void visitPacket(JoinRoomPacket packet);
void visitPacket(LeaveRoomPacket packet);
void visitPacket(LoginPacket packet);
@@ -27,5 +17,5 @@ public interface PacketVisitor {
void visitPacket(RoomListPacket packet);
void visitPacket(SendChatMessagePacket packet);
void visitPacket(ServerResponsePacket packet);
void visitPacket(HandshakePacket packet);
}

View File

@@ -1,23 +0,0 @@
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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,19 +1,19 @@
package network.protocol.packets;
import java.util.List;
import java.util.ArrayList;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
public class RoomListPacket extends Packet {
private final List<String> roomNames;
private final ArrayList<String> roomNames;
public RoomListPacket(List<String> roomNames) {
public RoomListPacket(ArrayList<String> roomNames) {
this.roomNames = roomNames;
}
public List<String> getRoomNames() {
public ArrayList<String> getRoomNames() {
return roomNames;
}

View File

@@ -19,67 +19,65 @@ public class Server implements PacketHandler {
private final DatagramSocket serverSocket;
private final Map<InetSocketAddress, ServerConnexion> connexions;
private final SocketReader reader;
private final Map<String, ArrayList<ServerConnexion>> rooms;
private final Map<String, ArrayList<ServerConnexion>> roomNames;
public Server(int port) throws SocketException {
this.serverSocket = new DatagramSocket(port);
this.connexions = new HashMap<>();
this.reader = new SocketReader(serverSocket, this);
this.rooms = new HashMap<>();
this.roomNames = new HashMap<>();
}
public ArrayList<String> getRoomNames() {
return rooms.keySet().stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
return roomNames.keySet().stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
public String getRoomName(ServerConnexion connexion) {
for (Map.Entry<String, ArrayList<ServerConnexion>> entry : rooms.entrySet()) {
if (entry.getValue().contains(connexion)) {
for (Map.Entry<String, ArrayList<ServerConnexion>> entry : roomNames.entrySet()) {
if(entry.getValue().contains(connexion)) {
return entry.getKey();
}
}
return null;
}
public boolean createRoom(String roomName, ServerConnexion connexion) {
if (rooms.containsKey(roomName)) {
return false;
public void createRoom(String roomName, ServerConnexion connexion) throws SocketException {
if(roomNames.containsKey(roomName)) {
throw new SocketException("Room already exists");
}
rooms.put(roomName, new ArrayList<>());
rooms.get(roomName).add(connexion);
return true;
roomNames.put(roomName, new ArrayList<>());
roomNames.get(roomName).add(connexion);
}
public boolean leaveRoom(ServerConnexion connexion) {
public void leaveRoom(ServerConnexion connexion) throws SocketException {
String roomName = getRoomName(connexion);
if (roomName != null) {
rooms.get(roomName).remove(connexion);
if(roomName != null) {
roomNames.get(roomName).remove(connexion);
// Remove the room if it is empty
if (rooms.get(roomName).isEmpty()) {
rooms.remove(roomName);
if(roomNames.get(roomName).isEmpty()) {
roomNames.remove(roomName);
}
return true;
return;
}
return false;
throw new SocketException("Room does not exist");
}
public boolean joinRoom(String roomName, ServerConnexion connexion) {
if (rooms.containsKey(roomName) && !rooms.get(roomName).contains(connexion)) {
rooms.get(roomName).add(connexion);
return true;
public void joinRoom(String roomName, ServerConnexion connexion) throws SocketException {
if(roomNames.containsKey(roomName) && !roomNames.get(roomName).contains(connexion)) {
roomNames.get(roomName).add(connexion);
return;
}
return false;
throw new SocketException("Room does not exist");
}
public boolean sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) {
public void sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) throws SocketException {
String roomName = getRoomName(connexion);
ChatMessagePacket chatPacket = new ChatMessagePacket(Instant.now(), connexion.getChatterName(),
packet.getContent());
if (roomName != null && rooms.containsKey(roomName)) {
rooms.get(roomName).forEach(con -> con.sendPacket(chatPacket));
return true;
ChatMessagePacket chatPacket = new ChatMessagePacket(Instant.now(), connexion.getChatterName(), packet.getContent());
if(roomName != null && roomNames.containsKey(roomName)) {
roomNames.get(roomName).forEach(con -> con.sendPacket(chatPacket));
return;
}
return false;
throw new SocketException("You are not in a room or the room does not exist");
}
public void close() {
@@ -91,20 +89,6 @@ public class Server implements PacketHandler {
.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)) {

View File

@@ -3,20 +3,12 @@ package server;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import network.SocketWriter;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
import network.protocol.packets.ChatMessagePacket;
import network.protocol.packets.CreateRoomPacket;
import network.protocol.packets.DisconnectPacket;
import network.protocol.packets.JoinRoomPacket;
import network.protocol.packets.LeaveRoomPacket;
import network.protocol.packets.LoginPacket;
import network.protocol.packets.RequestRoomListPacket;
import network.protocol.packets.RoomListPacket;
import network.protocol.packets.SendChatMessagePacket;
import network.protocol.packets.ServerResponsePacket;
import network.protocol.packets.*;
import network.protocol.packets.ServerResponsePacket.Response;
public class ServerConnexion implements PacketVisitor {
@@ -41,26 +33,43 @@ public class ServerConnexion implements PacketVisitor {
this.writer.sendPacket(packet, clientAddress);
} catch (IOException e) {
e.printStackTrace();
this.server.removeConnexion(this);
}
}
@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(CreateRoomPacket packet) {
boolean created = server.createRoom(packet.getRoomName(), this);
sendPacket(new ServerResponsePacket(created ? Response.RoomCreated : Response.RoomNotCreated));
try {
server.createRoom(packet.getRoomName(), this);
sendPacket(new ServerResponsePacket(Response.RoomCreated));
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.RoomNotCreated));
}
}
@Override
public void visitPacket(JoinRoomPacket packet) {
boolean joined = server.joinRoom(packet.getRoomName(), this);
sendPacket(new ServerResponsePacket(joined ? Response.RoomJoined : Response.RoomNotJoined));
try {
server.joinRoom(packet.getRoomName(), this);
sendPacket(new ServerResponsePacket(Response.RoomJoined));
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.RoomNotJoined));
}
}
@Override
public void visitPacket(LeaveRoomPacket packet) {
boolean left = server.leaveRoom(this);
sendPacket(new ServerResponsePacket(left ? Response.RoomLeft : Response.RoomNotLeft));
try {
server.leaveRoom(this);
sendPacket(new ServerResponsePacket(Response.RoomLeft));
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.RoomNotLeft));
}
}
@Override
@@ -80,22 +89,6 @@ public class ServerConnexion implements PacketVisitor {
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();
}
private void onDisconnect() {
this.server.removeConnexion(this);
System.out.println("[Server] Chatter " + chatterName + " disconnected !");
}
@Override
public void visitPacket(RoomListPacket packet) {
// I'm never supposed to receive this from the client
@@ -103,9 +96,12 @@ public class ServerConnexion implements PacketVisitor {
}
@Override
public void visitPacket(ChatMessagePacket packet) {
// I'm never supposed to receive this from the client
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
public void visitPacket(SendChatMessagePacket packet) {
try {
server.sendToRoom(this, packet);
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.MessageNotSent));
}
}
@Override
@@ -114,4 +110,10 @@ public class ServerConnexion implements PacketVisitor {
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
}
@Override
public void visitPacket(HandshakePacket packet) {
System.out.println("[Server] Handshake received from " + clientAddress);
sendPacket(new HandshakePacket());
}
}