306 lines
11 KiB
C
306 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <sys/wait.h>
|
|
#include <string.h>
|
|
|
|
#define ACCOUNT_SIZE 16
|
|
#define MAX_NAME_LEN 10
|
|
#define FILENAME "../Data/accounts.db"
|
|
|
|
// Couleurs pour l'affichage
|
|
#define RESET "\033[0m"
|
|
#define GREEN "\033[32m"
|
|
#define RED "\033[31m"
|
|
#define PINK "\033[35m"
|
|
#define BLUE "\033[34m"
|
|
|
|
|
|
/**
|
|
* @brief Lit les informations d'un compte à partir d'un fichier avec verrouillage.
|
|
*
|
|
* Cette fonction lit un compte spécifié par son index à partir du fichier
|
|
* spécifié par le descripteur de fichier. Elle place un verrou exclusif sur
|
|
* la section correspondante du fichier pour garantir qu'aucune autre
|
|
* opération ne puisse modifier le compte en même temps. Après la lecture,
|
|
* le verrou est libéré.
|
|
*
|
|
* @param fd Le descripteur de fichier du fichier contenant les comptes.
|
|
* @param index L'index du compte à lire (en termes de position dans le fichier).
|
|
* @param name Un pointeur vers une chaîne de caractères où le nom du compte sera stocké.
|
|
* @param balance Un pointeur vers un entier où le solde du compte sera stocké.
|
|
*/
|
|
void read_account(int fd, int index, char *name, int *balance) {
|
|
char buffer[ACCOUNT_SIZE];
|
|
|
|
// Placer un verrou exclusif sur la section du fichier correspondant au compte
|
|
lockf(fd, F_LOCK, ACCOUNT_SIZE);
|
|
|
|
// Se positionner à l'index du compte dans le fichier
|
|
lseek(fd, index * ACCOUNT_SIZE, SEEK_SET);
|
|
|
|
// Lire l'enregistrement du compte
|
|
read(fd, buffer, ACCOUNT_SIZE);
|
|
|
|
// Extraire le nom et le solde du compte
|
|
sscanf(buffer, "%7s %d", name, balance);
|
|
|
|
// Libérer le verrou après lecture
|
|
lockf(fd, F_ULOCK, ACCOUNT_SIZE);
|
|
}
|
|
|
|
/**
|
|
* @brief Écrit les informations d'un compte dans un fichier avec verrouillage.
|
|
*
|
|
* Cette fonction écrit un compte spécifié par son index dans le fichier
|
|
* spécifié par le descripteur de fichier. Avant d'écrire, elle formate les
|
|
* données du compte (nom et solde) pour les rendre compatibles avec la taille
|
|
* d'un enregistrement, puis place un verrou exclusif sur la section du fichier
|
|
* correspondante pour garantir que personne d'autre ne peut modifier ces
|
|
* données pendant l'écriture. Après l'écriture, le verrou est libéré.
|
|
*
|
|
* @param fd Le descripteur de fichier du fichier contenant les comptes.
|
|
* @param index L'index du compte à écrire (en termes de position dans le fichier).
|
|
* @param name Le nom du compte à écrire.
|
|
* @param balance Le solde du compte à écrire.
|
|
*
|
|
*/
|
|
void write_account(int fd, int index, const char *name, int balance) {
|
|
char buffer[ACCOUNT_SIZE];
|
|
|
|
// Formater la chaîne
|
|
snprintf(buffer, ACCOUNT_SIZE, "%-7s%8d", name, balance);
|
|
|
|
// S'assurer que le buffer est exactement de taille ACCOUNT_SIZE (16)
|
|
for (int i = strlen(buffer); i < ACCOUNT_SIZE - 1; i++) {
|
|
buffer[i] = ' ';
|
|
}
|
|
buffer[ACCOUNT_SIZE - 1] = '\n'; // Pour s'assurer que la ligne est terminée correctement (avec un saut de ligne)
|
|
|
|
// Placer un verrou exclusif sur la section du fichier correspondant au compte
|
|
lockf(fd, F_LOCK, ACCOUNT_SIZE);
|
|
|
|
// Se positionner à l'index du compte dans le fichier
|
|
lseek(fd, index * ACCOUNT_SIZE, SEEK_SET);
|
|
|
|
// Écrire l'enregistrement du compte
|
|
write(fd, buffer, ACCOUNT_SIZE);
|
|
|
|
// Libérer le verrou après écriture
|
|
lockf(fd, F_ULOCK, ACCOUNT_SIZE);
|
|
}
|
|
|
|
/**
|
|
* @brief Effectue une transaction aléatoire entre deux comptes avec verrouillage.
|
|
*
|
|
* Cette fonction simule une transaction entre deux comptes distincts sélectionnés
|
|
* aléatoirement. Elle garantit la cohérence des données en utilisant des verrouillages
|
|
* sur les sections correspondantes du fichier pour lire et écrire les informations
|
|
* des comptes. Le montant transféré est calculé aléatoirement et ne dépasse pas
|
|
* 20% du solde du compte source.
|
|
*
|
|
* @param fd Le descripteur de fichier du fichier contenant les comptes.
|
|
* @param account_count Le nombre total de comptes dans le fichier.
|
|
*
|
|
* @note Les comptes source et destination sont choisis de manière aléatoire,
|
|
* mais un compte ne peut pas transférer à lui-même.
|
|
* @note La transaction est annulée si le solde du compte source est insuffisant
|
|
* pour effectuer un transfert minimal.
|
|
*
|
|
*/
|
|
void perform_transaction(int fd, int account_count) {
|
|
int from_idx = rand() % account_count;
|
|
int to_idx = rand() % account_count;
|
|
|
|
// Sélectionner un autre compte si source et destination sont identiques
|
|
while (from_idx == to_idx) {
|
|
to_idx = rand() % account_count;
|
|
}
|
|
|
|
char from_name[MAX_NAME_LEN], to_name[MAX_NAME_LEN];
|
|
int from_balance, to_balance;
|
|
|
|
// Lire les informations des comptes source et destination
|
|
read_account(fd, from_idx, from_name, &from_balance);
|
|
read_account(fd, to_idx, to_name, &to_balance);
|
|
|
|
// Calculer un montant aléatoire à transférer (max 20% du solde source)
|
|
int max_transfer = from_balance / 5;
|
|
if (max_transfer > 0) {
|
|
int transfer_amount = rand() % max_transfer + 1;
|
|
|
|
// Effectuer la transaction
|
|
from_balance -= transfer_amount;
|
|
to_balance += transfer_amount;
|
|
|
|
// Écrire les nouveaux soldes dans le fichier
|
|
write_account(fd, from_idx, from_name, from_balance);
|
|
write_account(fd, to_idx, to_name, to_balance);
|
|
|
|
// Afficher un message de transaction réussie
|
|
printf(GREEN "Transaction : %s -> %s : %d€\n" RESET, from_name, to_name, transfer_amount);
|
|
} else {
|
|
// Afficher un message d'annulation de transaction si le solde est insuffisant
|
|
printf(RED "Transaction annulée : Fonds insuffisants pour %s\n" RESET, from_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Effectue un ensemble de transactions dans un processus.
|
|
*
|
|
* Cette fonction ouvre le fichier contenant les comptes en mode lecture/écriture
|
|
* et effectue un nombre donné de transactions aléatoires. Chaque transaction est
|
|
* réalisée en appelant la fonction `perform_transaction`. Une temporisation aléatoire
|
|
* est introduite entre les transactions pour simuler un comportement réaliste.
|
|
*
|
|
* @param transactions Le nombre de transactions à effectuer.
|
|
* @param account_count Le nombre total de comptes dans le fichier.
|
|
*
|
|
* @note La fonction ouvre le fichier spécifié par `FILENAME` en mode lecture/écriture.
|
|
* Elle s'assure de le fermer correctement à la fin.
|
|
*
|
|
* @warning Si l'ouverture du fichier échoue, la fonction interrompt le programme
|
|
* avec un appel à `exit(1)`.
|
|
*
|
|
* @details
|
|
* - La temporisation introduite par `usleep` (avec une durée aléatoire) contribue à la simulation d'un trafic réseau ou de retards dans les transactions.
|
|
*/
|
|
void process_transactions(int transactions, int account_count) {
|
|
// Ouvrir le fichier contenant les comptes en mode lecture/écriture
|
|
int fd = open(FILENAME, O_RDWR);
|
|
if (fd < 0) {
|
|
perror("Erreur : Impossible d'ouvrir le fichier");
|
|
exit(1);
|
|
}
|
|
|
|
// Effectuer le nombre spécifié de transactions
|
|
for (int i = 0; i < transactions; i++) {
|
|
perform_transaction(fd, account_count);
|
|
|
|
// Temporisation aléatoire pour simuler un délai
|
|
usleep(rand() % 100000);
|
|
}
|
|
|
|
// Fermer le fichier après avoir terminé les transactions
|
|
close(fd);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Calcule le solde total de tous les comptes dans le fichier.
|
|
*
|
|
* Cette fonction parcourt tous les comptes stockés dans le fichier spécifié
|
|
* par `FILENAME`, en lisant leurs soldes et en les additionnant pour produire
|
|
* un solde total. Elle ouvre le fichier en mode lecture seule, le parcourt
|
|
* compte par compte, puis le ferme avant de retourner le résultat.
|
|
*
|
|
* @return Le solde total de tous les comptes, ou -1 en cas d'erreur
|
|
* (par exemple, si l'ouverture du fichier échoue).
|
|
*
|
|
* @note La taille de chaque compte est définie par la constante `ACCOUNT_SIZE`.
|
|
*
|
|
* @warning Cette fonction ne valide pas les données des comptes lues depuis le fichier.
|
|
* Elle suppose que les données sont correctement formatées.
|
|
*
|
|
*/
|
|
int calculate_total_balance() {
|
|
// Ouvrir le fichier contenant les comptes en mode lecture seule
|
|
int fd = open(FILENAME, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror("Erreur : Impossible d'ouvrir le fichier");
|
|
return -1;
|
|
}
|
|
|
|
// Calculer le nombre de comptes à partir de la taille du fichier
|
|
off_t file_size = lseek(fd, 0, SEEK_END);
|
|
int account_count = file_size / ACCOUNT_SIZE;
|
|
|
|
// Initialiser le solde total
|
|
int total_balance = 0;
|
|
char name[MAX_NAME_LEN];
|
|
int balance;
|
|
|
|
// Parcourir et additionner les soldes de chaque compte
|
|
for (int i = 0; i < account_count; i++) {
|
|
read_account(fd, i, name, &balance);
|
|
total_balance += balance;
|
|
}
|
|
|
|
// Fermer le fichier avant de retourner le résultat
|
|
close(fd);
|
|
return total_balance;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// Vérifie que deux arguments sont passés en paramètre
|
|
if (argc != 3) {
|
|
fprintf(stderr, "Usage: %s <number_of_processes> <transactions_per_process>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
// Extraire le nombre de processus et le nombre de transactions par processus
|
|
int num_processes = atoi(argv[1]);
|
|
int transactions_per_process = atoi(argv[2]);
|
|
|
|
// Initialiser une graine aléatoire basée sur l'ID de processus et l'heure actuelle
|
|
// On aurait aussi pu utiliser la fonction rand() directement, mais rand_r() est plus sûr
|
|
// Car il permet d'éviter les interférences entre les processus
|
|
unsigned int seed = getpid() ^ time(NULL);
|
|
int random_value = rand_r(&seed);
|
|
|
|
// Calcul du solde total initial
|
|
int initial_balance = calculate_total_balance();
|
|
if (initial_balance < 0) {
|
|
return 1; // Erreur lors de l'ouverture du fichier
|
|
}
|
|
printf(PINK "Solde total initial : %10d€\n" RESET, initial_balance);
|
|
|
|
// Ouvrir le fichier pour déterminer le nombre de comptes
|
|
int fd = open(FILENAME, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror("Erreur : Impossible d'ouvrir le fichier");
|
|
return 1;
|
|
}
|
|
off_t file_size = lseek(fd, 0, SEEK_END);
|
|
close(fd);
|
|
|
|
// Calculer le nombre de comptes dans le fichier
|
|
int account_count = file_size / ACCOUNT_SIZE;
|
|
|
|
printf(BLUE "Nombre de comptes : %d\n" RESET, account_count);
|
|
|
|
// Créer les processus enfants pour effectuer les transactions
|
|
for (int i = 0; i < num_processes; i++) {
|
|
if (fork() == 0) {
|
|
process_transactions(transactions_per_process, account_count);
|
|
exit(0); // Chaque processus enfant se termine ici
|
|
}
|
|
}
|
|
|
|
// Attendre que tous les processus enfants se terminent
|
|
for (int i = 0; i < num_processes; i++) {
|
|
wait(NULL);
|
|
}
|
|
|
|
// Calculer le solde total final après toutes les transactions
|
|
int final_balance = calculate_total_balance();
|
|
if (final_balance < 0) {
|
|
return 1; // Erreur lors de l'ouverture du fichier
|
|
}
|
|
printf(PINK "Solde total final : %10d€\n" RESET, final_balance);
|
|
|
|
|
|
// Comparer le solde initial et le solde final
|
|
if (initial_balance != final_balance) {
|
|
printf(RED "Le solde final est différent du solde initial...\n" RESET);
|
|
}else{
|
|
printf(GREEN "Le solde final est identique au solde initial !\n" RESET);
|
|
}
|
|
|
|
printf("Tout les processus sont terminés !\n");
|
|
|
|
return 0;
|
|
}
|