mirror of
https://github.com/Akomry/makeyourownapp-jam.git
synced 2025-12-06 11:43:53 +00:00
feat: client/server working for adding lines, can undo locally, added a colorpicker and drawing in color is now a thing
This commit is contained in:
parent
a8b2a0ced6
commit
de645a8b1e
11 changed files with 939 additions and 40 deletions
175
graphical-app/src/main/java/fr/emiko/net/ClientTCP.java
Normal file
175
graphical-app/src/main/java/fr/emiko/net/ClientTCP.java
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package fr.emiko.net;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
|
||||
/**
|
||||
* Client TCP : envoie des chaines de caractères à un serveur et lit les chaines en retour.
|
||||
*/
|
||||
public class ClientTCP {
|
||||
/** Couleur rouge */
|
||||
public static final String RED = "\u001b[31m";
|
||||
/** Couleur bleue */
|
||||
public static final String BLUE = "\u001b[34m";
|
||||
/** Couleur standard */
|
||||
public static final String RST = "\u001b[0m";
|
||||
/** Fin de message */
|
||||
public static final String END_MESSAGE = "fin";
|
||||
|
||||
/**
|
||||
* Socket connecté au serveur
|
||||
*/
|
||||
protected Socket sock;
|
||||
|
||||
/**
|
||||
* Flux de caractères UTF-8 en sortie
|
||||
*/
|
||||
protected PrintStream out;
|
||||
|
||||
/**
|
||||
* Flux de caractères UTF-8 en entrée
|
||||
*/
|
||||
protected BufferedReader in;
|
||||
|
||||
/**
|
||||
* Chaine de caractères "ip:port" du client
|
||||
*/
|
||||
protected String ipPort;
|
||||
|
||||
/**
|
||||
* Le client est-il connecté ?
|
||||
*/
|
||||
protected boolean connected;
|
||||
|
||||
|
||||
/**
|
||||
* Programme principal [Déprécié]
|
||||
* @param args Arguments
|
||||
* @throws Exception Si la connexion échoue
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
ClientTCP client = new ClientTCP("localhost", 2024);
|
||||
Thread envoi = new Thread(client::sendLoop);
|
||||
Thread reception = new Thread(client::receiveLoop);
|
||||
envoi.start();
|
||||
reception.start();
|
||||
envoi.join();
|
||||
client.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Le constructeur ouvre la connexion TCP au serveur <code>host:port</code>
|
||||
* et récupère les flux de caractères en entrée {@link #in} et sortie {@link #out}
|
||||
*import static rtgre.chat.ChatApplication.LOGGER;
|
||||
* @param host IP ou nom de domaine du serveur
|
||||
* @param port port d'écoute du serveur
|
||||
* @throws IOException si la connexion échoue ou si les flux ne sont pas récupérables
|
||||
*/
|
||||
public ClientTCP(String host, int port) throws IOException {
|
||||
sock = new Socket(host, port);
|
||||
ipPort = "%s:%d".formatted(sock.getLocalAddress().getHostAddress(), sock.getLocalPort());
|
||||
this.connected = true;
|
||||
OutputStream os = sock.getOutputStream();
|
||||
InputStream is = sock.getInputStream();
|
||||
out = new PrintStream(os, true, StandardCharsets.UTF_8);
|
||||
in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8), 2048);
|
||||
Thread rcLoop = new Thread(this::receiveLoop);
|
||||
rcLoop.setDaemon(true);
|
||||
rcLoop.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie une chaine de caractères
|
||||
*
|
||||
* @param message chaine de caractères à transmettre
|
||||
* @throws IOException lorsqu'une erreur sur le flux de sortie est détectée
|
||||
*/
|
||||
public void send(String message) throws IOException {
|
||||
out.println(message);
|
||||
if (out.checkError()) {
|
||||
throw new IOException("Output stream error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter de connected
|
||||
* @return L'état de connected
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter de connected
|
||||
* @param connected L'utilisateur est-il connecté ?
|
||||
*/
|
||||
public void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attente d'une chaine de caractères en entrée.
|
||||
*
|
||||
* @return chaine de caractères reçue
|
||||
* @throws IOException lorsque la fin du flux est atteinte
|
||||
*/
|
||||
public String receive() throws IOException {
|
||||
String message = in.readLine();
|
||||
if (message == null) {
|
||||
throw new IOException("End of the stream has been reached");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fermeture de la connexion TCP
|
||||
*/
|
||||
public void close() {
|
||||
try {
|
||||
sock.close();
|
||||
this.connected = false;
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Boucle d'envoi de messages
|
||||
*/
|
||||
public void sendLoop() {
|
||||
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
|
||||
connected = true;
|
||||
try {
|
||||
while (connected) {
|
||||
String message = stdIn.readLine();
|
||||
if (message == null) { // fin du flux stdIn
|
||||
message = END_MESSAGE;
|
||||
}
|
||||
this.send(message);
|
||||
if (END_MESSAGE.equals(message)) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boucle de réception de messages
|
||||
*/
|
||||
public void receiveLoop() {
|
||||
connected = true;
|
||||
try {
|
||||
while (connected) {
|
||||
String message = this.receive();
|
||||
System.out.println("Message received: " + message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
graphical-app/src/main/java/fr/emiko/net/DrawClient.java
Normal file
68
graphical-app/src/main/java/fr/emiko/net/DrawClient.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package fr.emiko.net;
|
||||
|
||||
import fr.emiko.graphicalapp.HelloController;
|
||||
import javafx.application.Platform;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DrawClient extends ClientTCP{
|
||||
private final HelloController listener;
|
||||
|
||||
public DrawClient(String host, int port, HelloController listener) throws IOException {
|
||||
super(host, port);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Envoi d'un évènement, sérialisé dans sa représentation JSON, au serveur.
|
||||
* @param event L'évènement à envoyer
|
||||
*/
|
||||
public void sendEvent(Event event) {
|
||||
connected = true;
|
||||
try {
|
||||
String message = event.toJSON();
|
||||
if (message == null) { // fin du flux stdIn
|
||||
message = END_MESSAGE;
|
||||
}
|
||||
this.send(message);
|
||||
if (END_MESSAGE.equals(message)) {
|
||||
connected = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Boucle de réception des messages : chaque message est un évènement sérialisé en JSON, qui est transféré à ChatController.handleEvent(rtgre.modeles.Event) pour traitement.
|
||||
* Si le message n'est pas conforme (format JSON), la connection est stoppée.
|
||||
*/
|
||||
@Override
|
||||
public void receiveLoop() {
|
||||
try {
|
||||
while (connected) {
|
||||
String message = this.receive();
|
||||
if (listener != null) {
|
||||
Platform.runLater(() -> listener.handleEvent(Event.fromJSON(message)));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
connected = false;
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendAuthEvent(String login) {
|
||||
try {
|
||||
this.send(new Event(Event.AUTH, new JSONObject().put("username", login)).toJSON());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
234
graphical-app/src/main/java/fr/emiko/net/DrawServer.java
Normal file
234
graphical-app/src/main/java/fr/emiko/net/DrawServer.java
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
package fr.emiko.net;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Vector;
|
||||
|
||||
import fr.emiko.graphicsElement.Line;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class DrawServer {
|
||||
|
||||
private ServerSocket passiveSocket;
|
||||
private Vector<DrawClientHandler> clientList = new Vector<DrawClientHandler>();
|
||||
private Vector<Line> lines;
|
||||
public DrawServer(int port) throws IOException {
|
||||
passiveSocket = new ServerSocket(port);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
DrawServer server = new DrawServer(8090);
|
||||
server.acceptClients();
|
||||
}
|
||||
|
||||
public void acceptClients() {
|
||||
while (true) {
|
||||
try {
|
||||
Socket sock = passiveSocket.accept();
|
||||
handleNewClient(sock);
|
||||
} catch (IOException e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void removeClient(DrawClientHandler client) {
|
||||
clientList.remove(client);
|
||||
}
|
||||
|
||||
|
||||
private void handleNewClient(Socket sock) throws IOException {
|
||||
DrawClientHandler client = new DrawClientHandler(sock);
|
||||
clientList.add(client);
|
||||
|
||||
Thread clientLoop = new Thread(client::eventReceiveLoop);
|
||||
clientLoop.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ferme la connexion du serveur, en fermant la connexion auprès de tous ses clients, puis en fermant son socket en écoute passive.
|
||||
* @throws IOException si la connexion
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
for (DrawClientHandler client : clientList) {
|
||||
client.close();
|
||||
}
|
||||
passiveSocket.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class DrawClientHandler {
|
||||
|
||||
/** Message de fin d'une connexion */
|
||||
public static final String END_MESSAGE = "fin";
|
||||
/**
|
||||
* Socket connecté au client
|
||||
*/
|
||||
private Socket sock;
|
||||
/**
|
||||
* Flux de caractères en sortie
|
||||
*/
|
||||
private PrintStream out;
|
||||
/**
|
||||
* Flux de caractères en entrée
|
||||
*/
|
||||
private BufferedReader in;
|
||||
/**
|
||||
* Chaine de caractères "ip:port" du client
|
||||
*/
|
||||
private String ipPort;
|
||||
private User user;
|
||||
|
||||
/**
|
||||
* Initialise les attributs {@link #sock} (socket connecté au client),
|
||||
* {@link #out} (flux de caractères UTF-8 en sortie) et
|
||||
* {@link #in} (flux de caractères UTF-8 en entrée).
|
||||
*
|
||||
* @param sock socket connecté au client
|
||||
* @throws IOException si la connexion ne peut être établie ou si les flux ne peuvent être récupérés
|
||||
*/
|
||||
public DrawClientHandler(Socket sock) throws IOException {
|
||||
this.sock = sock;
|
||||
this.ipPort = "%s:%d".formatted(sock.getInetAddress().getHostAddress(), sock.getPort());
|
||||
OutputStream os = sock.getOutputStream();
|
||||
InputStream is = sock.getInputStream();
|
||||
this.out = new PrintStream(os, true, StandardCharsets.UTF_8);
|
||||
this.in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Boucle de réception d'évènement : réceptionne les messages reçus et les délèguent à `handleEvent(java.lang.String)` pour les interpréter
|
||||
*/
|
||||
public void eventReceiveLoop() {
|
||||
try {
|
||||
String message = null;
|
||||
while (!END_MESSAGE.equals(message)) {
|
||||
message = in.readLine();
|
||||
if (message == null) {
|
||||
break;
|
||||
}
|
||||
System.out.println("Réception de message : " + message);
|
||||
try {
|
||||
if (!handleEvent(message)) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Traitement d'un évènement. Ventile vers les méthodes traitant chaque type d'évènement.
|
||||
* @param message objet évènement sous la forme d'une chaine JSON brute de réception
|
||||
* @return `false` si l'évènement est de type Event.QUIT , `true` pour tous les autres types.
|
||||
* @throws JSONException si l'objet JSON n'est pas conforme
|
||||
* @throws IllegalStateException si l'authentification n'est pas effectuée
|
||||
*/
|
||||
private boolean handleEvent(String message) throws JSONException, IllegalStateException {
|
||||
Event event = Event.fromJSON(message);
|
||||
switch (event.getType()) {
|
||||
case Event.AUTH -> {
|
||||
doLogin(event.getContent());
|
||||
return true;
|
||||
}
|
||||
case Event.ADDLINE -> {
|
||||
doAddLine(event.getContent());
|
||||
return true;
|
||||
}
|
||||
case Event.DELLINE -> {
|
||||
doDelLine(event.getContent());
|
||||
return true;
|
||||
}
|
||||
case Event.LSTLINE -> {
|
||||
doSendLines();
|
||||
return true;
|
||||
}
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doDelLine(JSONObject content) {
|
||||
Line line = Line.fromJSONArray(content.getJSONArray("line"));
|
||||
this.user.getLines().remove(line);
|
||||
|
||||
sendAllOtherUsers(new Event("DELLINE", line.toJSONObject()));
|
||||
}
|
||||
|
||||
private void doAddLine(JSONObject content) {
|
||||
try {
|
||||
System.out.println(Line.fromJSONArray(content.getJSONArray("line")));
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
Line line = Line.fromJSONArray(content.getJSONArray("line"));
|
||||
this.user.getLines().add(line);
|
||||
sendAllOtherUsers(new Event("LINE", line.toJSONObject()));
|
||||
|
||||
}
|
||||
|
||||
private void sendAllOtherUsers(Event event) {
|
||||
for (DrawClientHandler client : clientList) {
|
||||
if (client.user != this.user) {
|
||||
System.out.println(client.user.getUsername());
|
||||
sendEvent(client, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEvent(DrawClientHandler client, Event event) {
|
||||
String jsonEvent = event.toJSON();
|
||||
client.out.println(jsonEvent);
|
||||
}
|
||||
|
||||
private void doSendLines() {
|
||||
Vector<Line> lines = new Vector<>();
|
||||
for (DrawClientHandler client: clientList) {
|
||||
for (Line line: client.user.getLines()) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
for (Line line: lines) {
|
||||
out.println(new Event("LINELST", line.toJSONObject()));
|
||||
}
|
||||
}
|
||||
|
||||
private void doLogin(JSONObject content) {
|
||||
this.user = new User(content.getString("username"));
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
sock.close();
|
||||
removeClient(this);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
50
graphical-app/src/main/java/fr/emiko/net/Event.java
Normal file
50
graphical-app/src/main/java/fr/emiko/net/Event.java
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package fr.emiko.net;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Event {
|
||||
public static final String AUTH = "AUTH";
|
||||
public static final String LSTLINE = "LSTLINE";
|
||||
public static final String ADDLINE = "ADDLINE";
|
||||
public static final String DELLINE = "DELLINE";
|
||||
public static final String LINE = "LINE";
|
||||
public static final String LINELST = "LINELST";
|
||||
|
||||
private String type;
|
||||
private JSONObject content;
|
||||
|
||||
public Event(String type, JSONObject content) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public JSONObject getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public JSONObject toJSONObject() {
|
||||
return new JSONObject()
|
||||
.put("type", type)
|
||||
.put("content", content);
|
||||
}
|
||||
|
||||
public String toJSON() {
|
||||
return toJSONObject().toString();
|
||||
}
|
||||
|
||||
public static Event fromJSON(String obj) {
|
||||
JSONObject jobj = new JSONObject(obj);
|
||||
String type = jobj.getString("type");
|
||||
JSONObject content = jobj.getJSONObject("content");
|
||||
return new Event(type, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toJSON();
|
||||
}
|
||||
}
|
||||
32
graphical-app/src/main/java/fr/emiko/net/User.java
Normal file
32
graphical-app/src/main/java/fr/emiko/net/User.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package fr.emiko.net;
|
||||
|
||||
import fr.emiko.graphicsElement.Line;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
public class User {
|
||||
private String username;
|
||||
private String hashedPassword = "";
|
||||
private Vector<Line> lines = new Vector<Line>();
|
||||
private boolean connected;
|
||||
|
||||
public User(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Vector<Line> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(Vector<Line> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue