mirror of
https://github.com/Akomry/sae302_applicom.git
synced 2025-12-06 08:43:54 +00:00
Merge branch 'dev-serveur' into 'dev'
feat(dev-serveur): Ajout services réseau de base See merge request iut_rt/but2/sae302-applicom/bouclyma!4
This commit is contained in:
commit
560f86c378
8 changed files with 796 additions and 9 deletions
|
|
@ -24,10 +24,8 @@ import net.synedra.validatorfx.TooltipWrapper;
|
||||||
import net.synedra.validatorfx.Validator;
|
import net.synedra.validatorfx.Validator;
|
||||||
import rtgre.chat.graphisme.ContactListViewCell;
|
import rtgre.chat.graphisme.ContactListViewCell;
|
||||||
import rtgre.chat.graphisme.PostListViewCell;
|
import rtgre.chat.graphisme.PostListViewCell;
|
||||||
import rtgre.modeles.Contact;
|
import rtgre.chat.net.ChatClient;
|
||||||
import rtgre.modeles.ContactMap;
|
import rtgre.modeles.*;
|
||||||
import rtgre.modeles.Message;
|
|
||||||
import rtgre.modeles.Post;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -38,6 +36,7 @@ import java.util.Date;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static rtgre.chat.ChatApplication.LOGGER;
|
import static rtgre.chat.ChatApplication.LOGGER;
|
||||||
|
|
@ -46,10 +45,11 @@ public class ChatController implements Initializable {
|
||||||
|
|
||||||
private static final Pattern LOGIN_PATTERN = Pattern.compile("^([a-z][a-z0-9]{2,7})$");
|
private static final Pattern LOGIN_PATTERN = Pattern.compile("^([a-z][a-z0-9]{2,7})$");
|
||||||
private static final Pattern HOST_PATTERN = Pattern.compile("/^[a-z]*((\\:?)\\d{1,5})?$/gm");
|
private static final Pattern HOST_PATTERN = Pattern.compile("/^[a-z]*((\\:?)\\d{1,5})?$/gm");
|
||||||
|
private final Pattern hostPortPattern = Pattern.compile("^([-.a-zA-Z0-9]+)(?::([0-9]{1,5}))?$");
|
||||||
public MenuItem hostAddMenuItem;
|
public MenuItem hostAddMenuItem;
|
||||||
public MenuItem avatarMenuItem;
|
public MenuItem avatarMenuItem;
|
||||||
public MenuItem aboutMenuItem;
|
public MenuItem aboutMenuItem;
|
||||||
public ComboBox hostComboBox;
|
public ComboBox<String> hostComboBox;
|
||||||
public TextField loginTextField;
|
public TextField loginTextField;
|
||||||
public ToggleButton connectionButton;
|
public ToggleButton connectionButton;
|
||||||
public ImageView avatarImageView;
|
public ImageView avatarImageView;
|
||||||
|
|
@ -66,6 +66,8 @@ public class ChatController implements Initializable {
|
||||||
private ObservableList<Contact> contactObservableList = FXCollections.observableArrayList();
|
private ObservableList<Contact> contactObservableList = FXCollections.observableArrayList();
|
||||||
private ObservableList<Post> postsObservableList = FXCollections.observableArrayList();
|
private ObservableList<Post> postsObservableList = FXCollections.observableArrayList();
|
||||||
Validator validatorLogin = new Validator();
|
Validator validatorLogin = new Validator();
|
||||||
|
private ChatClient client = null;
|
||||||
|
private PostVector postVector;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -147,7 +149,38 @@ public class ChatController implements Initializable {
|
||||||
contactMap.put(this.contact.getLogin(), this.contact);
|
contactMap.put(this.contact.getLogin(), this.contact);
|
||||||
LOGGER.info("Nouveau contact : " + contact);
|
LOGGER.info("Nouveau contact : " + contact);
|
||||||
LOGGER.info(contactMap.toString());
|
LOGGER.info(contactMap.toString());
|
||||||
|
Matcher matcher = hostPortPattern.matcher("localhost:2024");
|
||||||
|
matcher.matches();
|
||||||
|
String host = matcher.group(1);
|
||||||
|
int port = (matcher.group(2) != null) ? Integer.parseInt(matcher.group(2)) : 2024;
|
||||||
|
try {
|
||||||
|
this.client = new ChatClient(host, port, this);
|
||||||
|
initContactListView();
|
||||||
|
initPostListView();
|
||||||
|
clearLists();
|
||||||
|
contactMap.add(this.contact);
|
||||||
|
this.contact.setConnected(true);
|
||||||
|
initContactListView();
|
||||||
|
initPostListView();
|
||||||
|
this.statusLabel.setText("Connected to %s@%s:%s".formatted(this.contact.getLogin(), host, port));
|
||||||
|
} catch (IOException e) {
|
||||||
|
new Alert(Alert.AlertType.ERROR, "Erreur de connexion").showAndWait();
|
||||||
|
connectionButton.setSelected(false);
|
||||||
|
}
|
||||||
|
} else if (!connectionButton.isSelected()) {
|
||||||
|
clearLists();
|
||||||
|
this.client.close();
|
||||||
|
this.contact.setConnected(false);
|
||||||
|
statusLabel.setText("not connected to " + hostComboBox.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearLists() {
|
||||||
|
this.contactMap = new ContactMap();
|
||||||
|
this.postVector = new PostVector();
|
||||||
|
contactObservableList.clear();
|
||||||
|
postsObservableList.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkLogin(Check.Context context) {
|
private void checkLogin(Check.Context context) {
|
||||||
|
|
@ -185,10 +218,7 @@ public class ChatController implements Initializable {
|
||||||
contactsListView.setCellFactory(contactListView -> new ContactListViewCell());
|
contactsListView.setCellFactory(contactListView -> new ContactListViewCell());
|
||||||
contactsListView.setItems(contactObservableList);
|
contactsListView.setItems(contactObservableList);
|
||||||
File avatars = new File(getClass().getResource("avatars.png").toURI());
|
File avatars = new File(getClass().getResource("avatars.png").toURI());
|
||||||
Contact riri = new Contact("riri", false, avatars);
|
|
||||||
Contact fifi = new Contact("fifi", true, avatars);
|
Contact fifi = new Contact("fifi", true, avatars);
|
||||||
contactObservableList.add(riri);
|
|
||||||
contactMap.add(riri);
|
|
||||||
contactObservableList.add(fifi);
|
contactObservableList.add(fifi);
|
||||||
contactMap.add(fifi);
|
contactMap.add(fifi);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
53
chat/src/main/java/rtgre/chat/net/ChatClient.java
Normal file
53
chat/src/main/java/rtgre/chat/net/ChatClient.java
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package rtgre.chat.net;
|
||||||
|
|
||||||
|
import rtgre.chat.ChatController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static rtgre.chat.ChatApplication.LOGGER;
|
||||||
|
|
||||||
|
public class ChatClient extends ClientTCP {
|
||||||
|
|
||||||
|
private final ChatController listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param listener instance de ChatController liée au client
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public ChatClient(String host, int port, ChatController listener) throws IOException {
|
||||||
|
super(host, port);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveLoop() {
|
||||||
|
LOGGER.info(RED + "Boucle de réception de messages..." + RST);
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
String message = this.receive();
|
||||||
|
LOGGER.info(RED + "Réception: " + message + RST);
|
||||||
|
LOGGER.info(RED + message + RST);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.severe("[%s] %s".formatted(ipPort, e));
|
||||||
|
connected = false;
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger getLogger() {
|
||||||
|
return LOGGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatController getListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
192
chat/src/main/java/rtgre/chat/net/ClientTCP.java
Normal file
192
chat/src/main/java/rtgre/chat/net/ClientTCP.java
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package rtgre.chat.net;
|
||||||
|
|
||||||
|
import rtgre.chat.ChatController;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import static rtgre.chat.ChatApplication.LOGGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client TCP: envoie des chaines de caractères à un serveur et lit les chaines en retour.
|
||||||
|
* <p>
|
||||||
|
* Serveur netcat à lancer en face : <code>nc -k -l -p 2024 -v</code>
|
||||||
|
*/
|
||||||
|
public class ClientTCP {
|
||||||
|
|
||||||
|
public static final String RED = "\u001b[31m";
|
||||||
|
public static final String BLUE = "\u001b[34m";
|
||||||
|
public static final String RST = "\u001b[0m";
|
||||||
|
public static final String END_MESSAGE = "fin";
|
||||||
|
|
||||||
|
/*
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
InputStream is = ClientTCP.class.getClassLoader()
|
||||||
|
.getResource("rtgre/chat/logging.properties").openStream();
|
||||||
|
LogManager.getLogManager().readConfiguration(is);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.log(Level.INFO, "Cannot read configuration file", e);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
/*
|
||||||
|
ClientTCP client = new ClientTCP("localhost", 2024);
|
||||||
|
|
||||||
|
// Essai simple d'émission / réception d'une chaine de caractères
|
||||||
|
String message = "Hello World!";
|
||||||
|
System.out.println(BLUE + "Envoi :" + message + RST);
|
||||||
|
client.send(message);
|
||||||
|
message = client.receive();
|
||||||
|
System.out.println(RED + "Réception: " + message + RST);
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
protected boolean connected;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public ClientTCP(String host, int port) throws IOException {
|
||||||
|
System.out.printf("Connexion à [%s:%d]%n", host, port);
|
||||||
|
sock = new Socket(host, port);
|
||||||
|
ipPort = "%s:%d".formatted(sock.getLocalAddress().getHostAddress(), sock.getLocalPort());
|
||||||
|
LOGGER.info("[%s] Connexion établie vers [%s:%d]".formatted(ipPort, host, port));
|
||||||
|
this.connected = true;
|
||||||
|
LOGGER.fine("[%s] Recuperation des flux d'octets en entree et sortie".formatted(ipPort));
|
||||||
|
OutputStream os = sock.getOutputStream();
|
||||||
|
InputStream is = sock.getInputStream();
|
||||||
|
LOGGER.fine("[%s] Conversion flux d'octets en flux de caractères UTF-8".formatted(ipPort));
|
||||||
|
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 {
|
||||||
|
LOGGER.finest("send: %s".formatted(message));
|
||||||
|
out.println(message);
|
||||||
|
if (out.checkError()) {
|
||||||
|
throw new IOException("Output stream error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
LOGGER.finest("receive: %s".formatted(message));
|
||||||
|
if (message == null) {
|
||||||
|
throw new IOException("End of the stream has been reached");
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fermeture de la connexion TCP
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
LOGGER.info("[%s] Fermeture de la connexion".formatted(ipPort));
|
||||||
|
try {
|
||||||
|
sock.close();
|
||||||
|
this.connected = false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.finest("[%s] %s".formatted(ipPort, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void sendLoop() {
|
||||||
|
System.out.println(BLUE + "Boucle d'envoi de messages..." + RST);
|
||||||
|
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
connected = true;
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
System.out.println(BLUE + "Votre message (\"fin\" pour terminer) : " + RST);
|
||||||
|
String message = stdIn.readLine();
|
||||||
|
if (message == null) { // fin du flux stdIn
|
||||||
|
message = END_MESSAGE;
|
||||||
|
}
|
||||||
|
System.out.println(BLUE + "Envoi: " + message + RST);
|
||||||
|
this.send(message);
|
||||||
|
if (END_MESSAGE.equals(message)) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.severe(e.toString());
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveLoop() {
|
||||||
|
System.out.println(RED + "Boucle de réception de messages..." + RST);
|
||||||
|
connected = true;
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
String message = this.receive();
|
||||||
|
System.out.println(RED + "Réception: " + message + RST);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.severe("[%s] %s".formatted(ipPort, e));
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
210
chat/src/main/java/rtgre/server/ChatServer.java
Normal file
210
chat/src/main/java/rtgre/server/ChatServer.java
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
package rtgre.server;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Programme serveur qui renvoie les chaines de caractères lues jusqu'à recevoir le message "fin"
|
||||||
|
*/
|
||||||
|
public class ChatServer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ChatServer.class.getCanonicalName());
|
||||||
|
|
||||||
|
private Vector<ChatClientHandler> clientList;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
InputStream is = ChatServer.class.getClassLoader()
|
||||||
|
.getResource("logging.properties").openStream();
|
||||||
|
LogManager.getLogManager().readConfiguration(is);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.log(Level.INFO, "Cannot read configuration file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
ChatServer server = new ChatServer(2024);
|
||||||
|
server.acceptClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket passif en écoute
|
||||||
|
*/
|
||||||
|
private ServerSocket passiveSock;
|
||||||
|
|
||||||
|
public ChatServer(int port) throws IOException {
|
||||||
|
passiveSock = new ServerSocket(port);
|
||||||
|
LOGGER.info("Serveur en écoute " + passiveSock);
|
||||||
|
clientList = new Vector<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
for (ChatClientHandler client : clientList) {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
passiveSock.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boucle d'attente des clients
|
||||||
|
*/
|
||||||
|
public void acceptClients() {
|
||||||
|
int clientCounter = 1;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
LOGGER.info("Attente du client n°%02d".formatted(clientCounter));
|
||||||
|
Socket sock = passiveSock.accept();
|
||||||
|
LOGGER.info("[%s:%d] Connexion établie (client n°%02d)"
|
||||||
|
.formatted(sock.getInetAddress().getHostAddress(), sock.getPort(), clientCounter));
|
||||||
|
handleNewClient(sock);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.severe(e.toString());
|
||||||
|
}
|
||||||
|
clientCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeClient(ChatClientHandler client) {
|
||||||
|
clientList.remove(client);
|
||||||
|
LOGGER.fine("Client [%s] retiré de la liste (%d clients connectés)"
|
||||||
|
.formatted(client.getIpPort(), clientList.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector<ChatClientHandler> getClientList() {
|
||||||
|
return clientList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSocket getPassiveSocket() {
|
||||||
|
return passiveSock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prise en charge d'un nouveau client
|
||||||
|
*
|
||||||
|
* @param sock socket connecté au client
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void handleNewClient(Socket sock) throws IOException {
|
||||||
|
ChatClientHandler client = new ChatClientHandler(sock);
|
||||||
|
Thread clientLoop = new Thread(client::echoLoop);
|
||||||
|
clientLoop.start();
|
||||||
|
clientList.add(client);
|
||||||
|
LOGGER.fine("Ajout du client [%s] dans la liste (%d clients connectés)"
|
||||||
|
.formatted(client.getIpPort(), clientList.size()));
|
||||||
|
//client.echoLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChatClientHandler {
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public ChatClientHandler(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 écho : renvoie tous les messages reçus.
|
||||||
|
*/
|
||||||
|
public void echoLoop() {
|
||||||
|
try {
|
||||||
|
String message = null;
|
||||||
|
while (!END_MESSAGE.equals(message)) {
|
||||||
|
message = in.readLine();
|
||||||
|
if (message == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOGGER.info("[%s] Réception de : %s".formatted(ipPort, message));
|
||||||
|
LOGGER.info("[%s] Envoi de : %s".formatted(ipPort, message));
|
||||||
|
//out.println(message);
|
||||||
|
sendAllOtherClients(this, message);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.severe("[%s] %s".formatted(ipPort, e));
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String message) throws IOException {
|
||||||
|
LOGGER.finest("send: %s".formatted(message));
|
||||||
|
out.println(message);
|
||||||
|
if (out.checkError()) {
|
||||||
|
throw new IOException("Output stream error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpPort() {
|
||||||
|
return ipPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendAllOtherClients(ChatClientHandler fromClient, String message) {
|
||||||
|
for (ChatClientHandler client : clientList) {
|
||||||
|
if (!client.equals(this)) {
|
||||||
|
LOGGER.fine(clientList.toString());
|
||||||
|
LOGGER.fine("Envoi vers [%s] : %s".formatted(client.getIpPort(), message));
|
||||||
|
try {
|
||||||
|
client.send("[%s] %s".formatted(fromClient.getIpPort(), message));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.severe("[%s] %s".formatted(client.getIpPort(), e));
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive() throws IOException {
|
||||||
|
String message = in.readLine();
|
||||||
|
LOGGER.finest("receive: %s".formatted(message));
|
||||||
|
if (message == null) {
|
||||||
|
throw new IOException("End of the stream has been reached");
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
LOGGER.info("[%s] Fermeture de la connexion".formatted(ipPort));
|
||||||
|
try {
|
||||||
|
sock.close();
|
||||||
|
removeClient(this);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
|
||||||
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
|
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
|
||||||
|
|
||||||
# Nom du fichier de logs
|
# Nom du fichier de logs
|
||||||
java.util.logging.FileHandler.pattern=../out/java%u.log
|
java.util.logging.FileHandler.pattern=../target/java%u.log
|
||||||
|
|
||||||
# Format de logs plus compact sur 1 seule ligne
|
# Format de logs plus compact sur 1 seule ligne
|
||||||
java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL | %4$-7s | %2$s | %5$s %6$s%n
|
java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL | %4$-7s | %2$s | %5$s %6$s%n
|
||||||
|
|
|
||||||
169
chat/src/test/java/rtgre/chat/net/ChatClientTest.java
Normal file
169
chat/src/test/java/rtgre/chat/net/ChatClientTest.java
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
package rtgre.chat.net;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import java.util.logging.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||||
|
|
||||||
|
class ChatClientTest {
|
||||||
|
|
||||||
|
static Class classe = ChatClient.class;
|
||||||
|
static String module = "rtgre.chat.net";
|
||||||
|
|
||||||
|
@DisplayName("01-Structure de ChatClient")
|
||||||
|
@Nested
|
||||||
|
class StructureTest {
|
||||||
|
|
||||||
|
static List<String> methodesSignatures;
|
||||||
|
static List<String> constructeursSignatures;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() {
|
||||||
|
// Les méthodes
|
||||||
|
Method[] methodes = classe.getDeclaredMethods();
|
||||||
|
methodesSignatures = Arrays.asList(methodes).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||||
|
Constructor<?>[] constructeurs = classe.getConstructors();
|
||||||
|
constructeursSignatures = Arrays.asList(constructeurs).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Stream<Arguments> attributsProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("listener", "rtgre.chat.ChatController", Modifier.PRIVATE | Modifier.FINAL)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des attributs")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("attributsProvider")
|
||||||
|
void testDeclarationAttributs(String nom, String type, int modifier) throws NoSuchFieldException {
|
||||||
|
Field field = classe.getDeclaredField(nom);
|
||||||
|
Assertions.assertEquals(type, field.getType().getName(),
|
||||||
|
"Type " + nom + " erroné : doit être " + type);
|
||||||
|
Assertions.assertEquals(modifier, field.getModifiers(),
|
||||||
|
"Visibilité " + nom + " erronée : doit être " + modifier);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> constructeursProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("public %s.ChatClient(java.lang.String,int,rtgre.chat.ChatController) throws java.io.IOException")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des constructeurs")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("constructeursProvider")
|
||||||
|
void testConstructeurs1(String signature) {
|
||||||
|
Assertions.assertTrue(constructeursSignatures.contains(String.format(signature, module)),
|
||||||
|
String.format("Constructeur non déclaré : doit être %s\nalors que sont déclarés %s",
|
||||||
|
signature, constructeursSignatures));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> methodesProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("receiveLoop", "public void %s.ChatClient.receiveLoop()"),
|
||||||
|
arguments("getLogger", "public java.util.logging.Logger %s.ChatClient.getLogger()")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des méthodes")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("methodesProvider")
|
||||||
|
void testDeclarationMethodes1(String nom, String signature) {
|
||||||
|
Assertions.assertTrue(methodesSignatures.contains(String.format(signature, module, module)),
|
||||||
|
String.format("Méthode non déclarée : doit être %s\nalors que sont déclarés %s",
|
||||||
|
signature, methodesSignatures));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DisplayName("02-Connexion/déconnexion (port=1810)")
|
||||||
|
@Nested
|
||||||
|
class ConnexionTest {
|
||||||
|
static int port = 1810;
|
||||||
|
static LocalServer server;
|
||||||
|
static ExecutorService executorService;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() throws IOException {
|
||||||
|
server = new LocalServer(port);
|
||||||
|
executorService = new ThreadPoolExecutor(1, 1, 2,
|
||||||
|
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
|
||||||
|
executorService.submit(server::acceptClients);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void close() throws IOException {
|
||||||
|
server.passiveSocket.close();
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("Boucle de réception")
|
||||||
|
@Test
|
||||||
|
void testReceiveLoop() throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
ChatClient client = new ChatClient("localhost", port, null);
|
||||||
|
Logger logger = client.getLogger();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Handler handler = new StreamHandler(out, new SimpleFormatter());
|
||||||
|
logger.addHandler(handler);
|
||||||
|
|
||||||
|
|
||||||
|
server.clientOut.println("bonjour");
|
||||||
|
executorService.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
handler.flush();
|
||||||
|
String logMsg = out.toString();
|
||||||
|
client.close();
|
||||||
|
Assertions.assertTrue(logMsg.contains("bonjour"),
|
||||||
|
"Le message reçu doit être loggué");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalServer {
|
||||||
|
public ServerSocket passiveSocket;
|
||||||
|
public Socket clientSocket;
|
||||||
|
public PrintStream clientOut;
|
||||||
|
public BufferedReader clientIn;
|
||||||
|
public boolean stop = false;
|
||||||
|
|
||||||
|
public LocalServer(int port) throws IOException {
|
||||||
|
passiveSocket = new ServerSocket(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acceptClients() {
|
||||||
|
while (!stop) {
|
||||||
|
try {
|
||||||
|
clientSocket = passiveSocket.accept();
|
||||||
|
clientOut = new PrintStream(clientSocket.getOutputStream(), true, StandardCharsets.UTF_8);
|
||||||
|
clientIn = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8), 2048);
|
||||||
|
} catch (IOException e) {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
chat/src/test/java/rtgre/chat/net/ClientTCPTest.java
Normal file
126
chat/src/test/java/rtgre/chat/net/ClientTCPTest.java
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package rtgre.chat.net;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import rtgre.chat.net.ClientTCP;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||||
|
|
||||||
|
class ClientTCPTest {
|
||||||
|
|
||||||
|
static Class classe = ClientTCP.class;
|
||||||
|
static String module = "rtgre.chat.net";
|
||||||
|
|
||||||
|
|
||||||
|
@DisplayName("01-Structure de ClientTCP")
|
||||||
|
@Nested
|
||||||
|
class StructureTest {
|
||||||
|
static Method[] methodes;
|
||||||
|
static List<String> methodesSignatures;
|
||||||
|
static List<String> constructeursSignatures;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() {
|
||||||
|
// Les méthodes
|
||||||
|
methodes = classe.getDeclaredMethods();
|
||||||
|
methodesSignatures = Arrays.asList(methodes).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||||
|
Constructor<?>[] constructeurs = classe.getConstructors();
|
||||||
|
constructeursSignatures = Arrays.asList(constructeurs).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Stream<Arguments> attributsProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("sock", "java.net.Socket", Modifier.PROTECTED),
|
||||||
|
arguments("out", "java.io.PrintStream", Modifier.PROTECTED),
|
||||||
|
arguments("in", "java.io.BufferedReader", Modifier.PROTECTED),
|
||||||
|
arguments("ipPort", "java.lang.String", Modifier.PROTECTED),
|
||||||
|
arguments("connected", "boolean", Modifier.PROTECTED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des attributs")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("attributsProvider")
|
||||||
|
void testDeclarationAttributs(String nom, String type, int modifier) throws NoSuchFieldException {
|
||||||
|
Field field = classe.getDeclaredField(nom);
|
||||||
|
Assertions.assertEquals(type, field.getType().getName(),
|
||||||
|
"Type " + nom + " erroné : doit être " + type);
|
||||||
|
Assertions.assertEquals(modifier, field.getModifiers(),
|
||||||
|
"Visibilité " + nom + " erronée : doit être " + modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Stream<Arguments> constructeursProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("public %s.ClientTCP(java.lang.String,int) throws java.io.IOException")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des constructeurs")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("constructeursProvider")
|
||||||
|
void testConstructeurs1(String signature) {
|
||||||
|
Assertions.assertTrue(constructeursSignatures.contains(String.format(signature, module)),
|
||||||
|
String.format("Constructeur non déclaré : doit être %s\nalors que sont déclarés %s",
|
||||||
|
signature, constructeursSignatures));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> methodesProvider() {
|
||||||
|
return Stream.of(
|
||||||
|
arguments("isConnected", "public boolean %s.ClientTCP.isConnected()"),
|
||||||
|
arguments("send", "public void %s.ClientTCP.send(java.lang.String) throws java.io.IOException"),
|
||||||
|
arguments("receive", "public java.lang.String %s.ClientTCP.receive() throws java.io.IOException"),
|
||||||
|
arguments("close", "public void %s.ClientTCP.close()")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@DisplayName("Déclaration des méthodes")
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("methodesProvider")
|
||||||
|
void testDeclarationMethodes1(String nom, String signature) {
|
||||||
|
Assertions.assertTrue(methodesSignatures.contains(String.format(signature, module, module)),
|
||||||
|
String.format("Méthode non déclarée : doit être %s\nalors que sont déclarés %s",
|
||||||
|
signature, methodesSignatures));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("02-Connexion/déconnexion (port=1800)")
|
||||||
|
@Nested
|
||||||
|
class ConnexionTest {
|
||||||
|
static int port = 1800;
|
||||||
|
static ServerSocket passiveSocket;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() throws IOException {
|
||||||
|
passiveSocket = new ServerSocket(1800);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void close() throws IOException {
|
||||||
|
passiveSocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("Connexion+deconnexion")
|
||||||
|
@Test
|
||||||
|
void testConnexion() throws IOException {
|
||||||
|
ClientTCP client = new ClientTCP("localhost", port);
|
||||||
|
Assertions.assertNotNull(client, "Connexion impossible");
|
||||||
|
Assertions.assertTrue(client.isConnected(), "Etat de connexion erroné");
|
||||||
|
client.close();
|
||||||
|
Assertions.assertFalse(client.isConnected(), "Etat de connexion erroné");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
chat/src/test/java/rtgre/server/ChatServerTest.java
Normal file
7
chat/src/test/java/rtgre/server/ChatServerTest.java
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package rtgre.server;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ChatServerTest {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue