Problème fork et pipe en C

Fermé
dragnyon - Modifié le 20 déc. 2021 à 12:53
mamiemando Messages postés 33372 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 22 novembre 2024 - 20 déc. 2021 à 13:24
Bonjour,

Je fais appel à votre aide car j'ai un problème dans mon programme. Je m'explique :
  • j'ai un programme
    serveur.c
    et
    client.c
    ;
  • dans le
    serveur.c
    , j'utilise
    fork()
    pour que mon serveur écoute deux clients mais du coup les données ne sont pas partagées ;
  • on m'a conseillé d'utiliser
    pipe()
    , mais cela fait bloquer un des deux clients et je n'arrive pas à comprendre pourquoi, (je suis novice en programmation).


Merci d'avance de votre aide car je suis un peu desépéré.

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <arpa/inet.h>

struct sockaddr_in serv;
int fd;


char envoi[100] = "";
char recu[100]="";
int THI = 0;


void* recevoir(void* infojeu);
void* env(void* cartej);


void error(char *msg)
{
    perror(msg);
    exit(0);
}


void main(int argc, char** argv){

THI = strtol(argv[2], NULL, 10);
fd = socket(AF_INET, SOCK_STREAM, 0);
serv.sin_family = AF_INET;
serv.sin_port = htons(strtol(argv[1], NULL, 10));
inet_pton(AF_INET, "localhost", &serv.sin_addr);
if(connect(fd, (struct sockaddr *)&serv, sizeof(serv))<0)
{
 error("Serveur fermer");
}
  pthread_t threadRecv;
 pthread_t threadSend;
   pthread_create(&threadRecv, NULL, recevoir, (void*)&recu);
  pthread_create(&threadSend, NULL, env, (void*)&envoi);
 while(1)
 {

 };


}

void* recevoir(void* infojeu)
{
 while(1)
 {
  if(recv(fd,recu,100,0)>0)
  {
   printf("%s",recu);
   bzero(recu,100);
   printf("\nAppuyez sur Entrée pour jouer: \n(vous jouerez votre plus petite carte)\n");
   
  }
  else
  {
   printf("vous etes deconnecte du serveur\n");
   exit(1);
  }
 }

}

void* env(void* cartej)
{
 while(1)
 {
  //printf("\nAppuyez sur Entrée pour jouer: \n(vous jouerez votre plus petite carte)\n");
        envoi[0] = '0'+THI;
        envoi[1] = '-';
        fgets(envoi+2, 98, stdin);

     send(fd, envoi, strlen(envoi), 0);
     bzero(envoi,100);
 };
}

serveur.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#define NB_J 2
#define NB_MANCHE 10
#define SIZE_BUFF 50


struct sockaddr_in serv;
int fd;
int conn;
char message[SIZE_BUFF] = "";
int nbjoueur=0;
int joueur[NB_J];
int manchecourante=2;
int nbcoups=0;
char cartesjoueur[NB_J][NB_MANCHE];
char cartejoue [NB_J*NB_MANCHE];

void initcarte();
void affichecartes();
char* cartedejoueur(char* infoj,int joueur,int manchec);
void joue(int j, int coup);//joue la premiere carte du tableau du joueur qui joue
int verif();//verifie si la manche est perdu ou gagné
void trie();//trie les carte de chaque joueur dans l'ordre croissant

void main(int argc, char** argv)
{

 printf("Démarrage du jeu...\n");
 serv.sin_family = AF_INET;
 serv.sin_port = htons(strtol(argv[1], NULL, 10));
 serv.sin_addr.s_addr = INADDR_ANY;
 fd = socket(AF_INET, SOCK_STREAM, 0);
 bind(fd, (struct sockaddr *)&serv, sizeof(serv));
 listen(fd,5);
 initcarte();
 int nbcoups=0;
   //int comIntern[2];
  // pipe(comIntern);
 while(nbjoueur<NB_J)
 {
  conn = accept(fd, (struct sockaddr *)NULL, NULL);
  joueur[nbjoueur]=conn;
  cartesjoueur[nbjoueur][0]=conn;
  nbjoueur++;
  printf("Le joueur %d viens de se connecter !\n",conn);
  printf("Il y a %d joueurs\n",nbjoueur);
  printf("%d joueurs - %d %d %d",nbjoueur,joueur[0],joueur[1]);
   }
   affichecartes();
 for (int t=0; t<NB_J; t++)
 {
 
  if(fork()==0)
  {
   char infoj[SIZE_BUFF]="";
   send(joueur[t], cartedejoueur(infoj,(joueur[t]),manchecourante), SIZE_BUFF, 0);
   while(recv(joueur[t], message, SIZE_BUFF, 0)) 
   {
    printf("\nLe joueur %d a jouer \n",joueur[t]);
    joue((joueur[t]-joueur[0]), nbcoups);
    nbcoups++;
   // close(comIntern[0]);
   //   write(comIntern[1],cartejoue,( NB_J*NB_MANCHE));
    //close(comIntern[1]);
    for(int i=0;i<2;i++)
    { 
     send(joueur[i], cartedejoueur(infoj,i,manchecourante), SIZE_BUFF, 0);
     //send(joueur[i], cartejoue, SIZE_BUFF, 0);
    }
    if(nbcoups==(NB_J*manchecourante))
    {
     if(verif()==0)
     {
      manchecourante++;
      
      for(int p=0;p<2;p++)
      { 
       send(joueur[p], cartejoue, SIZE_BUFF, 0);
       send(joueur[p], "Manche gagner, niveau suivant", SIZE_BUFF, 0);
      }
      bzero(cartejoue,(NB_J*NB_MANCHE));
     }
    }  
   }
  }
 }
 while(1)
 {
  /*
  char buffer[100];
  close(comIntern[1]);
  if(read(comIntern[0],buffer,100))
  {
   //printf("data%s",buffer);     
  }
  close(comIntern[0]);*/
 } 
}

void initcarte()
{
 srand(time(NULL));
 for(int i=0;i<NB_J;i++)
 {
  for(int j=1;j<=NB_MANCHE;j++)
  { 
   cartesjoueur[i][j]=rand()%100; 
  } 
 }
 trie();
}

void trie()
{
 int temp;
 for(int r=0;r<100;r++)
 {
  for(int i=0;i<NB_J;i++)
  {
   for(int j=1;j<NB_MANCHE;j++)
   {
    if(cartesjoueur[i][j]>cartesjoueur[i][j+1])
    {
     temp=cartesjoueur[i][j];
     cartesjoueur[i][j]=cartesjoueur[i][j+1];
     cartesjoueur[i][j+1]=temp;
    }
   }  
  }
 }


}





void affichecartes()
{
 for(int i=0;i<NB_J;i++)
 {
  printf("\nCartes du joueur %d:",cartesjoueur[i][0]);
  for(int j=1;j<NB_MANCHE;j++)
  {
   printf("%d  ",cartesjoueur[i][j]); 
  }
  printf("\n"); 
 }
} 


//renvoi dans un tableau les cartes du joueur d'id passé en paramètre 
char* cartedejoueur(char* infoj,int joueur, int manchec)
{
 
 for(int i=1;i<=manchec;i++)
  snprintf(infoj,SIZE_BUFF,"%s-%d",infoj,cartesjoueur[joueur][i]);
 return infoj;
}

void joue(int j, int coup)
{
 int i =0;
 int t=1;
 do 
 {
  
  if(cartesjoueur[j][t]!=0)
  {
   printf("ca joue\n");
   cartejoue[coup]=cartesjoueur[j][t];
   cartesjoueur[j][t]=0;
   i=1;
  }
  else 
  {
   t++;
  }
 }
 while(i==0);
 printf("cartes jouées");
 for(int q=0;q<10;q++)
 {
  printf("%d-",cartejoue[q]);
 
 }
 
}


int verif()
{
 int j=0;
 for(int i=0;i<(NB_J*NB_MANCHE);i++)
 {
  if(cartejoue[i+1]<cartejoue[i])
  {
   j++;
  }
 }
 return j;

}

1 réponse

mamiemando Messages postés 33372 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 22 novembre 2024 7 802
20 déc. 2021 à 13:24
Bonjour,

Ce que je te propose, c'est d'utiliser
glib
et
gio
, car cela permet de faire plus simplement et proprement un client/serveur ultra performante. Pas besoin de s'embêter avec des threads, les problèmes de timeouts, tout est géré pour toi par gio. La seule difficulté, c'est d'installer glib et gio (sous Linux c'est facile, sous Windows, je ne sais pas mais normalement c'est faisable).

Pré-requis

Dans ce qui suit, je présuppose que tu as installé
glib-2.0
et
gio-2.0
. Ces deux librairies existent sous Windows et Linux.

Pour la phase de compilation je suppose que tu as installé un compilateur (e.g.
gcc
) et dans mon cas je lance la compilation via une
Makefile
, ce qui nécessite d'installer
make
. Tu peux utiliser ce que tu as l'habitude d'utiliser.

Sous Linux (plus précisément sous debian/ubuntu) on installe ces dépendances comme suit :

sudo apt update
sudo apt install libglib2.0-dev glib-networking make


Pour windows, voir ce lien.

client.c

Dans cet exemple, le client envoie un couple (id, d) avec d = id + 1000.

// Based on https://stackoverflow.com/questions/24476188/asynchronous-gio-server-client

#include <glib.h>
#include <gio/gio.h>
#include <stdio.h>
#include <string.h>
#include "common.h"

#define TIMEOUT_MS     10000 // 10 seconds
#define BUFFER_SIZE    1024

struct client_conn_t {
    GSocketClient     * client;     // Needed to get GSocketClient from main()
    GSocketConnection * connection; // Needed to return GSocketConnection to main()
    GIOChannel        * channel;
};

static void print_connection(GSocketConnection * connection) {
    GSocketAddress *sockaddr = g_socket_connection_get_remote_address(connection, NULL);
    GInetAddress *addr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(sockaddr));
    guint16 port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(sockaddr));
    g_print("Connection to %s:%d\n", g_inet_address_to_string(addr), port);
}

static gboolean callback_read(
    GIOChannel    * channel,
    GIOCondition    condition,
    gpointer        user_data
) {
    TRACE_FUNCTION;
    gsize len;
    GIOStatus ret;
    GSocketConnection * connection = G_SOCKET_CONNECTION(user_data);
    GError            * error = NULL;

    if (condition & G_IO_HUP) {
        g_error("The server has closed the connection!\n");
        return FALSE;
    }

    gchar buffer[BUFFER_SIZE]; // Larger than sizeof(reply_t)
    gsize bytes_read;
    ret = g_io_channel_read_chars(channel, buffer, BUFFER_SIZE, &len, &error);

    switch (ret) {
        case G_IO_STATUS_ERROR:
            g_print("Error reading: %s\n", error->message);
            g_object_unref(connection);
            return FALSE;
        case G_IO_STATUS_EOF:
            g_print("The server has closed the connection\n");
            g_object_unref(connection);
            return FALSE;
    }

    if (len) {
        g_print("Read %u bytes (expected: %d)\n", len, sizeof(reply_t));
        print_reply((reply_t *) buffer);
    }

    return TRUE;
}

gboolean callback_send(gpointer data) {
    TRACE_FUNCTION;
    struct client_conn_t *c = data;
    if (g_socket_connection_is_connected(c->connection)) {
        static unsigned query_id = 0;
        query_id++;
        query_t q = {
            .query_id = query_id,
            .data = query_id + 1000
        };
        print_query(&q); // The server should reply (query_id, data) == (query_id, query_id)

        // Sync version
        //return send_bytes(c->connection, c->channel, &q, sizeof(q));

        // Async version. Pass a callback instead of NULL to use your own GAsyncReadyCallback.
        send_bytes_async(c->connection, &q, sizeof(q), NULL);
    } else {
        g_print("callback_send : not connected\n");
    }
    return TRUE; // Keep this GSource, the server may start soon...
}

void callback_connect(
    GObject      * source_object,
    GAsyncResult * res,
    gpointer       user_data
) {
    TRACE_FUNCTION;
    GError * error = NULL;

    // Set GSocketConnection
    struct client_conn_t * c = (struct client_conn_t *) user_data;
    GSocketConnection * connection = g_socket_client_connect_to_host_finish(c->client, res, &error);
    c->connection = connection;

    // Check whether the GSocketConnection is established.
    if (error) {
        g_error(error->message);
        return;
    }

    // Print connection
    print_connection(connection);

    // Install watch
    g_object_ref(connection); // ADDED
    GSocket * socket = g_socket_connection_get_socket(connection);

    if (!socket) {
        g_error("Cannot get socket\n");
        return;
    }

    // From here, the code is the same in the client and the server.

    gint fd = g_socket_get_fd(socket);
    GIOChannel * channel = g_io_channel_unix_new(fd);
    c->channel = channel; // We'll need it for callback_send
    g_print("channel = %p\n", channel);

    if (!channel) {
        g_error("Cannot create channel\n");
        return;
    }

    // Exchange binary data with the server
    g_io_channel_set_encoding(channel, NULL, &error);
    if (error) {
        g_error("Cannot set encoding: %s", error->message);
        return;
    }

    // G_IO_IN: There is data to read.
    // G_IO_OUT: Data can be written (without blocking).
    // G_IO_PRI: There is urgent data to read.
    // G_IO_ERR: Error condition.
    // G_IO_HUP: Hung up (the connection has been broken, usually for pipes and sockets).
    // G_IO_NVAL: Invalid request. The file descriptor is not open.

    // Triggered whenever the client can read data from the socket
    if (!g_io_add_watch(channel, G_IO_IN  | G_IO_HUP, callback_read, connection)) {
        g_error("Cannot watch\n");
        return;
    }

    /*
    // Triggered whenever the client can write into the socket
    if (!g_io_add_watch(channel, G_IO_OUT | G_IO_HUP, callback_write, connection)) {
        g_error("Cannot watch\n");
        return;
    }
    */

    g_timeout_add(500, callback_send, c);
    g_print("OK!\n");
}

gboolean timeout_callback(gpointer data) {
    TRACE_FUNCTION;
    g_print("timeout_callback: LEAVING\n");
    g_main_loop_quit((GMainLoop*) data);
    return FALSE;
}

int main(int argc, char **argv) {
#if !GLIB_CHECK_VERSION(2, 35, 0)
    g_type_init ();
#endif

    struct client_conn_t *c = g_malloc0(sizeof *c);
    c->client = g_socket_client_new();

    // NO context must be used!
    // Indeed g_io_add_watch installs watch in the DEFAULT context.
    GMainLoop * loop = g_main_loop_new(NULL, FALSE);
    g_timeout_add(TIMEOUT_MS, timeout_callback, loop);
    g_socket_client_connect_to_host_async(c->client, "localhost", PORT, NULL, callback_connect, c);
    g_main_loop_run(loop);

    // Free, close etc...
    g_print("Free\n");
    g_io_stream_close(G_IO_STREAM(c->connection), NULL, NULL);
    g_object_unref(c->client);
    g_object_unref(c->connection);
    g_main_loop_unref(loop);
    g_free(c);
    return 0;
}


server.c

Dans cet exemple, le serveur renvoie au client adéquat le couple (id, d-1000) :

// Based on:
//   https://stackoverflow.com/questions/9513327/gio-socket-server-client-example
//   http://www.linuxjournal.com/node/8545/print
//
// Compilation:
//   gcc `pkg-config --cflags --libs glib-2.0 gio-2.0` server.c -o server

#include <glib.h>
#include <gio/gio.h>
#include <string.h>
#include "common.h"
#define BUFFER_SIZE   1024

static gboolean callback_read(
    GIOChannel    * channel,
    GIOCondition    condition,
    gpointer        user_data
) {
    TRACE_FUNCTION;
//    gsize len;
    gssize len;
    GIOStatus ret;
    GSocketConnection * connection = G_SOCKET_CONNECTION(user_data);
    GError            * error = NULL;

    if (condition & G_IO_HUP) {
        g_print("The client has disconnected! I feel alone so I stop to listen.\n");
        return FALSE; // The client has disconnected abruptly, remove this GSource
    }

    gchar buffer[BUFFER_SIZE]; // Larger than sizeof(reply_t)
    gsize bytes_read;
    /* INCORRECT
//    ret = g_io_channel_read_chars(channel, buffer, BUFFER_SIZE, &len, &error);
    */
    GInputStream * istream = g_io_stream_get_input_stream(G_IO_STREAM(connection));
    g_print("istream = %x\n", istream);
    len = g_input_stream_read (istream, buffer, BUFFER_SIZE, NULL, &error);
    g_print("len = %d\n", len);
        switch (len) {
        case -1:
            g_error("Error reading: %s\n", error->message);
            g_object_unref(connection);
            return FALSE;
        case 0:
            g_print("Client disconnected\n");
            return FALSE; // The client has closed the connection gracefully, remove this GSource
        default:
            break;
    }

    query_t * q = (query_t *) buffer;
    print_query(q);

    // TODO Run the query in a thread, then send the reply back to the client.
    // TODO If data >= 0 : send every 100ms either data+1,data+2,data+3 with a dedicated thread.
    // TODO If data <  0 : send every 150ms either data-1,data-2,data-3 with a dedicated thread.
    static int reply_id = 0;
    reply_t r = {
        .query_id = q->query_id,
        .data = q->data - 1000
    };

    print_reply(&r);

    // Sync version
    //return send_bytes(connection, channel, &r, sizeof(r));

    // Async version. Pass a callback instead of NULL to use your own GAsyncReadyCallback.
    send_bytes_async(connection, &r, sizeof(r), NULL);


    return TRUE;
}

// This function will get called everytime a client attempts to connect
gboolean callback_connect(
    GSocketService    * service,
    GSocketConnection * connection,
    GObject           * source_object,
    gpointer            user_data
) {
    TRACE_FUNCTION;
    GError * error = NULL;

    // Print connection
    GSocketAddress *sockaddr = g_socket_connection_get_remote_address(connection, NULL);
    GInetAddress *addr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(sockaddr));
    guint16 port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(sockaddr));
    g_print("New Connection from %s:%d\n", g_inet_address_to_string(addr), port);

    // Install watch
    g_object_ref(connection);
    GSocket * socket = g_socket_connection_get_socket(connection);

    // From here, the code is the same in the client and the server.

    gint fd = g_socket_get_fd(socket);
    GIOChannel * channel = g_io_channel_unix_new(fd);

    if (!channel) {
        g_error("Cannot create channel\n");
        return TRUE;
    }

    // Exchange binary data with the client
    g_io_channel_set_encoding(channel, NULL, &error);
    if (error) {
        g_error("Cannot set encoding: %s", error->message);
        return TRUE;
    }

    // G_IO_IN: There is data to read.
    // G_IO_OUT: Data can be written (without blocking).
    // G_IO_PRI: There is urgent data to read.
    // G_IO_ERR: Error condition.
    // G_IO_HUP: Hung up (the connection has been broken, usually for pipes and sockets).
    // G_IO_NVAL: Invalid request. The file descriptor is not open.

    // Triggered whenever the server can read data from the socket
    if (!g_io_add_watch(channel, G_IO_IN | G_IO_HUP, callback_read, connection)) {
        g_error("Cannot watch\n");
        return TRUE;
    }

    return FALSE;
}

int main(int argc, char **argv) {
    // For old glib
#if !GLIB_CHECK_VERSION(2, 35, 0)
    g_type_init ();
#endif

    // socket()
    GError * error = NULL;
    GSocketService * service = g_socket_service_new();

    g_socket_listener_add_inet_port(
        (GSocketListener *) service,
        PORT,
        NULL,
        &error
    );

    if (error) {
        g_error(error->message);
        return 1;
    }

    // connect()
    // Listen to the 'incoming' signal
    g_signal_connect(
        service,
        "incoming",
        G_CALLBACK(callback_connect),
        NULL
    );

    // Start the socket service
    g_socket_service_start(service);

    // Run the main loop
    g_print("Listening on localhost:%d\n", PORT);
    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(loop);

    // This cannot be reached will we do not configure Cancelable
    g_print("Free\n");
    g_main_loop_unref(loop);
    g_socket_service_stop(service);
    g_free(service);

    return 0;
}


Makefile

CC     = gcc
CFLAGS = `pkg-config --cflags --libs glib-2.0 gio-2.0` -g -W

all:
	$(CC) $(CFLAGS) -c common.c -o common.o 
	$(CC) $(CFLAGS) client_async.c -o client_async common.o 
	$(CC) $(CFLAGS) server_async.c -o server_async common.o


Compilation

make


Quelques recommandations

Puisque tu dis débuter, je me permets quelques conseils pour améliorer la qualité de ton code :
  • évite les variables globales (surtout quand tu utilises des threads !) ;
  • soigne l'indentation (espace autour des opérateurs, indentation = 4 espaces...; espace derrière les virgules/points virgules) ;
  • soigne le nommage (je recommande d'adopter le style suivant :
    ma_variable
    ,
    ma_fonction
    ,
    typedef struct ma_structure_t { ... } ma_structure_t;
    ,
    #define MA_CONSTANTE
    ).


Bonne chance
0