Thread & Semaphore

Résolu
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014 - 10 janv. 2014 à 15:56
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 - 28 juin 2016 à 17:00
Bonjour,

Je voudrais vérifier avec vous si j'ai bien compris un principe de base du multithreading.

Je prends mon exemple. Je dois créer un proxy. Sauf que je veux lui imposer une contrainte de connexions maximum simultanées pour les clients.
L'idée c'est donc d'utiliser un semaphore sur des threads qui gère les connexions.
Mais le truc, c'est que mon sémaphores va limiter le nombre de thread (le nombre de connexion) lancés en même temps.

Prenons l'exemple d'un nombre max de 5. Si un 6ième client ce connecte, il doit être mis en attente. Donc il faut qu'un thread le capte, mais attende d'avoir le feu vert de la semaphore n'est ce pas ??!

En plus clair,
dois-je avoir plus de thread pour récupérer les connexions clients, que le nombre max de connexion défini pour ma semaphore ?

Un élément de réponse :
Je pense qu'il faudrait que je sinde la partie écoute de connexions client, avec celle du traitement des requêtes des clients. Et ainsi pouvoir appliquer mon sémaphore qu'au traitement et laisser libre le nombre de connexions clients. Il seront juste mis en attente par la semaphore qui devrait réguler le nombre de traitement simultannées...
Suis-je à côté de mes pompes, ou non ?! :p


Merci d'avance pour votre aide.

NotaBene : Vu que c'est une question de compréhension et que je pense avoir expliqué la chose correctement. Je ne pense pas avoir à vous montrer un bout de code. Surtout que pour le moment je réfléchie à mon projet. J'ai rien commencé.
Mais s'il vous en faut un absolument, je pourrai toujours écrire un p'tit truc vite fait pour illustrer (encore une fois j'en vois pas trop l'intérêt).
A voir également:

21 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 10/01/2014 à 18:13
Prenons l'exemple d'un nombre max de 5. Si un 6ième client ce connecte, il doit être mis en attente. Donc il faut qu'un thread le capte, mais attende d'avoir le feu vert de la semaphore n'est ce pas ??!
Théoriquement, oui.

Je pense qu'il faudrait que je sinde la partie écoute de connexions client, avec celle du traitement des requêtes des clients. Et ainsi pouvoir appliquer mon sémaphore qu'au traitement et laisser libre le nombre de connexions clients. Il seront juste mis en attente par la semaphore qui devrait réguler le nombre de traitement simultannées...
Il faut surtout que tu évites de trop te prendre la tête. Généralement, on évite de recoder la roue. Je te conseille fortement l'utilisation de boost::asio (en plus y a des tutos), qui te simplifieront la vie.


__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par cptpingu le 10/01/2014 à 17:50
Il faut surtout que tu évites de trop te prendre la tête. Généralement, on évite de recoder la roue. Je te conseille fortement l'utilisation de boost::asio (en plus y a des tutos), qui te simplifieront la vie.

Merci, je vais aller y jeter un coup d'oeil.
C'est mon plus gros problème que de vouloir réinventer tout depuis le début à chaque fois. J'ai toujours tendance a oublier que d'autres on codé des fonctions ou des bibliothèques qui simplifies la vie.

Ps : Je met le sujet en "résolu" pour le moment et si besoin je reviendrais dessus.
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par theGrimReaper le 10/01/2014 à 19:47
J'aimerai savoir que fait concrètement la fonction Listen().
http://www.commentcamarche.net/contents/1053-les-fonctions-de-l-api-socket#la-fonction-listen

Parce que d'après toutes les doc que j'ai lu dessus, ils disent qu'elle permet de mettre en attente une connexion. Et le deuxième paramêtre défini le nombre max de connexions qu'on peut mettre en attente.

Seulement malgré tous les tests que j'ai pu faire, j'arrive pas à en comprendre le fonctionnement.

Pour revenir à mon exemple de proxy que je veux faire :
Je veux threader les connexions des clients pour y coller une semaphore et ainsi limiter le nombre de connexions simultanées.
Sauf que la fonction listen semble le faire d'après son deuxième paramêtre. Donc j'me suis dit, c'est parfait pas besoin de se prendre la tête avec les threads et sémaphores.
Sauf que mes tests ont échoués. Elle me limitait pas le nombre de connexion.
Et donc si je reprends mon idée de base, et que j'essais de lancer deux threads sur le même port pour écouter et gérer les connexions. Ca plante parce que j'ai pas le droit de redéfinir un socket pour le même port puisse qu'il y en a déjà un...

J'ai sûrement mal compris comment marchait listen()...
Serait-il possible que listen ne marche pas parce que je test en locahost et que du coup elle reçoit la même adresse quand j'essais de me co par deux onglets sur firefox..?


Voilà donc pour résumer un peu,
Comment dois-je m'y prendre pour pouvoir gérer plusieurs connexions simultanément ? Et pourquoi surtout...


Et pour te répondre cptpingu sur ton boost::asio,
ça à l'air tentant et bien plus simple. Mais j'suis obligé de faire ce proxy dans le cadre d'un projet pour le bahu et en C (pas C++ comme boost).
Donc obligé de se taper les sockets en brut sous unix à coup de socket(), blind(), listen(), read(), write()...
Mais c'est cool, tu m'as quand même fait découvrir cette bibliothèque. Je retiendrai qu'elle existe au cas où j'en ai besoin à l'occasion.
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 10/01/2014 à 21:17
Tout d'abord, je précise que je fais plus de C++ que de C, et que j'ai plus de compétences en multi-threading qu'en réseau. Donc j'espère ne pas te dire de bêtise.

Quand tu écris un petit client + server en C avec des sockets, on fait la chose suivante dans le serveur :
- D'abord on décrit les caractéristiques de la connexion dans un "sockaddr_in" (port, type de connexion (ici du TCP), etc...)
- On attribue au socket ces caractéristiques en "liant" la conf réseau au socket, via la fonction "bind" (qui veut dire "lier" ^^).
- Listen permet de configuer le socket et de lui dire: "Tu vas écouter sur le port donné dans sockaddr, et dès que quelquechose est prêt à être lu, tu me le dis". Pour vulgariser (c'est pas ça du tout, mais pour te faire une image): ça appelle une fonction kernel qui tourne d'elle même dans son coin, comme si c'était une routine parallèle, et qui se charge de te notifier dès qu'une connexion est là. Le deuxième argument de la fonction est spécial et je te l'explique après.
- La fonction accept est une fonction bloquante, qui te fait attendre tant qu'elle n'est pas débloqué. Cette fonction est débloquée par "listen" (en fait par le kernel qui t'envoit un signal, mais tu ne le vois pas). En débloquant "accept", "listen" t'envoit une copie du socket avec les données reçues. Donc si tu fais un while + accept, tu as ton tout premier serveur qui va répondre à tout requête qu'on lui fera :).
Bien entendu, tu ne peux lire qu'une seule connexion à la fois. Si tu as plusieurs clients, ils attendront leur tour, tout simplement !

Revenons à "listen", son deuxième argument permet de donner le nombre de connexion "pending", à traiter. Je m'explique. C'est en fait le nombre de connexions non traitées maximal que tu peux avoir. Dit comme ça, ce n'est pas encore très clair.
Imagine la chose suivante: Je fais un "listen(socket, 5)". Si jamais avant d'atteindre "accept", je reçois 6 connexions, j'en garde en stock 5, et la 6ème est perdue.
(Le 6ème client se prendra un "connexion refused").
Si bien sur, j'ai 10 clients, mais que j'ai le temps de traiter leurs requêtes suffisamment vite, même avec 5 connexion max, ça passera. On parle bien de connexion *simultanées*. En gros, imagine que le second argument est une pile. A chaque connexion reçue tu empiles. A chaque "accept" tu dépiles. Tant que la vitesse de dépilage est inférieur à la vitesse d'empilage, tu t'en sortiras. Si la vitesse d'empilage est trop grande (plein de clients), alors tout ce qui sera empilé en trop sera perdu (et c'est à toi de définir la limite d'empilage).

Maintenant, le souci c'est que tu ne peux traiter qu'une seule requête à la fois. Ce qu'on fait, c'est que l'on reçoit toujours nos accept dans le "papa", et une fois les données récupérées (plus précisement une fois la copie de socket récupérée, aussi appelé "session socket"), on crée un "fils", on lui donne le socket et il se démerde. On recommence un "accept", et on donne la prochaine tâches à un nouveau "fils", etc...


Tu peux voir un excellent exemple basique ici: http://en.wikibooks.org/wiki/C_Programming/Networking_in_UNIX
Pour poursuivre ta lecture, je te conseille ensuite ceci: http://beej.us/guide/bgipc/output/html/multipage/unixsock.html

Enfin, une fois le mono-connexion assimilé, tu peux passer au multi-connexion, avec cet exemple: http://www.tutorialspoint.com/unix_sockets/socket_server_example.htm (tout en bas).


__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par theGrimReaper le 10/01/2014 à 21:58
Parfait encore une fois. Merci bien cptpingu.
Clair et limpide.
Une grande part d'obscurité vient de se lever dans mon esprit. xD
C'est fou comme des connaissances un poil obscures peuvent se lier et devenir claires, uniquement en apprennant un p'tit détail en plus...

Le pire c'est que je viens de revenir pour poster un élément de réponse car j'ai compris au fur et à mesure de mes tests que le while : il faut le balancer sur le accept et pas le tout (socket, bind, etc).

Donc si tu fais un while + accept, tu as ton tout premier serveur qui va répondre à tout requête qu'on lui fera :).
Voilà, exactement.

Maintenant, le souci c'est que tu ne peux traiter qu'une seule requête à la fois. Ce qu'on fait, c'est que l'on reçoit toujours nos accept dans le "papa", et une fois les données récupérées (plus précisement une fois la copie de socket récupérée, aussi appelé "session socket"), on crée un "fils", on lui donne le socket et il se démerde. On recommence un "accept", et on donne la prochaine tâches à un nouveau "fils", etc...
Ok, ben dans ce cas je vais faire ça aussi.


Je vais regarder la doc que tu viens de me filer.
Encore une fois merci,
et je repasse si j'ai encore un éventuel problème.
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
10 janv. 2014 à 22:07
Question :
Peut-on faire un fork dans un thread ?

Et si c'est possible, est-ce que les "pères" de chaque thread ont le même pid ?
Parce que le processus père est le même les threads lancés par lui même (c'est la différence fondamentale qui différencie les forks des threads si j'ai bien compris).
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
10 janv. 2014 à 22:18
C'est possible, mais je n'en vois pas l'intérêt...
On utilise soit l'un, soit l'autre.
Dans ton cas, fait au plus simple. Utilise listen + fork.

Un fork, clone l'application en cours. Tu peux tout à fait provoquer un clonage de l'application, dans un thread. Tu te retrouves alors avec deux processus, qui sont tous les deux identiques (mais de pid différent), et au même "endroit", dans un thread que tu as appelé (chacun possède sa copie du thread, donc le tid est différent). Vu qu'il y a eu recopie, tout est différent. Les deux processus sont différents, et les threads qu'ils possèdent aussi. Chaque processus et thread sont uniques.

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par theGrimReaper le 10/01/2014 à 22:48

//----------------------------------------------------------------------------------
// Phase de connexion
//----------------------------------------------------------------------------------

//attente de connexions client
printf("\tServeur en attente d'une connexion ...\n\n");
if(listen(sock, 2) != 0)
perror(">> Erreur de la fonction Listen");
else
{
while(1)
{
//Acceptation de la connexion
int sock_com = accept(sock, (struct sockaddr *) 0, (int *) 0);
if(sock_com == -1)
perror(">> Erreur de la fct Accept");
else
{
printf("\t...CONNEXION ETABLIE...\n\n");

//création d'un fils pour effectuer le traitement de chaque connexions
int pidFils;

pidFils = fork();
if(pidFils < 0) // (pid < 0) => Erreur
perror("Erreur de création du fils pour la géstion de la connexion");
else if(pidFils == 0)// (pid == 0) => Fils
{
//fils
gestionConnexionClientTCP(sock_com);
exit(0);
}
}
}
}


C'est qu'un bout de code partiel de mon programme actuel pour illustrer mon fork.
Ce morceau de code ce trouve dans un thread qui est lancé par le programme principal. Après la création du socket, et du bind.

Ce qu'il faut pour comprendre ce que j'ai voulu faire, c'est que mon thread récupère en paramètre le numéro de port à écouter.
Donc mon idée était de lancé un thread pour chacuns des ports autorisés. Sauf que les forks duppliques toute ce qu'il y a en mémoire. Donc si j'ai lancé 2 threads par le programme principal (ex : port 23 SMTP et 80 HTTP), je vais me retrouver avec 4 threads en mémoire (dont deux identiques).
N'est-ce pas ?


Donc, soit il existe une autre façon de faire que j'ignore pour faire ce que je veux qui est plus viable et moins alambiqué, soit faudrait-il mieux pas que je fasse encore une fois un thread dans le thread (ça fera une arborescence de thread certes) mais au moins comme ça j'aurai moins de dupplication mémoire et ça évitera de s'enbêter avec les éventuels "fils fou" ?

J'suis désolé cptpingu,
parce que je sens que c'est encore une fois toi que je vais solliciter... ^^

EDIT :
Ou je peux faire l'inverse. Lancer mes différentes fonctions pour écouter chacuns des ports par des fils, et lancer les threads pour la gestion des connexions reçues dans ces fonctions...

Un avis ? Parce que j'ai pas l'expérience pour savoir ce qui se fait courament dans la pratique.
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
Modifié par theGrimReaper le 10/01/2014 à 23:12
J'ai fait ça avec des threads uniquement et ça marche plutôt bien.
Après si quelqu'un à une meilleurs façon de faire je suis tout ouï...

Merci pour l'aide,
je mets le sujet en résolu (pour l'instant xD).


//----------------------------------------------------------------------------------
// Phase de connexion
//----------------------------------------------------------------------------------

//attente de connexions client
printf("\tServeur en attente d'une connexion ...\n\n");
if(listen(sock, 2) != 0)
perror(">> Erreur de la fonction Listen");
else
{
while(1)
{
//Acceptation de la connexion
int sock_com = accept(sock, (struct sockaddr *) 0, (int *) 0);
if(sock_com == -1)
perror(">> Erreur de la fct Accept");
else
{
printf("\t...CONNEXION ETABLIE...\n\n");

//CREATION d'un thread fils pour effectuer le traitement de chaque connexions ?????????
pthread_t thread_gestionCo;

if(pthread_create(&thread_gestionCo, NULL, thread_GestionConnexionClientTCP, (void*) sock_com) != 0)//si la création a foiré : renvoi != 0
printf("ERREUR de creation du thread\n");
}
}
}
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 11/01/2014 à 04:14
Salut.

Alors pour gérer le multi port proprement, je te propose une solution qui n'utilise ni thread, ni fork ! (Mais qui pourrait être amélioré pour utiliser des threads, mon but étant de te montrer qu'on ne gère pas des ports en ajoutant des threads ou fork).
Listen c'est bien quand on débute (car facile) mais pas terrible. En effet, il demande les files descriptors ("fd" pour les intimes) un à un, alors qu'on pourrait en demander plusieurs (cas de connexions multiples). On utilise donc dans la "vraie vie" du "epoll" (un peu plus dur à utiliser, mais bien plus puissant).

Listen à le comportement suivant:
- Accept demande à listen s'il y a un fd disponible. Listen lui dit oui et en renvoit un.
- Accept le traite.
- Accept redemande s'il peut traiter un fd.
- Accept demande à listen s'il y a un fd disponible. Listen lui dit oui et en renvoit un.
- Accept le traite.
- Accept demande à listen s'il y a un fd disponible. Listen lui dit non.
- Accept attend.
- Etc...

Epoll à le comportement suivant:
- Epoll_wait demande à epoll la liste des fd disponibles. Epoll lui en renvoit 2.
- Epoll_wait ajoute les 2 dans sa liste.
- Epoll_wait ajoute traite les 2 de la liste.
- Epoll_wait demande à epoll la liste des fd disponibles. Epoll lui en renvoit 0.
- Epoll_wait attend.
- Etc...

Pour comprendre le fonctionnement de epoll, voici ce que l'on fait généralement:
- Ca commence comme pour "listen". On crée un socket, et on bind une conf dessus.
- On rend le socket non-bloquant (via la fonction "fcntl").
- On ajoute le socket dans la liste de epoll.
- Si on veut plus de ports, on ajoute autant de socket que l'on veut de port.
- On fait une boucle principale, mais au lieu de "listen", on utilise "epoll_wait" (même principe).
- Si epoll_wait se débloque, on obtient une liste de socket prêt à être utilisé (tous les clients de tous les ports sont mélangés).
- Lorsque l'on trouve une connection qui correspond au socket physique, on fait un accept dessus afin d'obtenir le socket session. Ce socket est ajouté sur la liste de epoll.
- On cherche ensuite dans la liste renvoyé par epoll, tous les sockets sessions, qui ont été mis là précédemment. On lis les données et on les traite, puis on retire les sockets traités de la liste.
- On recommence.

Pour résumer:

listen: listen + accept en boucle
epoll: epoll_add_physique (avec listen non bloquant sur chacun des sockets physiques) puis epoll_wait en boucle. Dans la boucle: accept + epoll_add_session puis traitement des socket sessions mis dans epoll.

Dans mon exemple je gère une infinité de port (le port max est 65535) dynamiquement. Je ne gère pas les threads, mais je t'ai mis l'endroit ou il faudra modifier un peu le code pour les gérer.

Voici le client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MAXRCVLEN 500
#define PORTNUM 2343

int main(int argc, char *argv[])
{
  char buffer[MAXRCVLEN + 1]; /* +1 so we can add null terminator */
  int len, mysocket;
  struct sockaddr_in dest;

  mysocket = socket(AF_INET, SOCK_STREAM, 0);

  memset(&dest, 0, sizeof(dest));                /* zero the struct */
  dest.sin_family = AF_INET;
  dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* set destination IP number - localhost, 127.0.0.1*/
  dest.sin_port = htons(atoi(argv[1]));                /* set destination port number */

  if (connect(mysocket, (struct sockaddr *)&dest, sizeof (struct sockaddr)))
  {
    perror("connect error!");
    exit(1);
  }

  send(mysocket, "Coucou c'est moi le client", strlen("Coucou c'est moi le client"), 0);

  len = recv(mysocket, buffer, MAXRCVLEN, 0);
  if (len > 0)
  {
    /* We have to null terminate the received data ourselves */
    buffer[len] = '\0';
    printf("Received %s (%d bytes).\n", buffer, len);
  }
  else
  {
    if (len < 0)
      perror("recv");
    else
      printf("Server closed connection\n");
  }

  close(mysocket);
  return EXIT_SUCCESS;
}


Et le serveur:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

#define MAXEVENTS 64

/*
  Assume that tab finish by 0
*/
static int is_in_tab(int val, int* tab)
{
  while (tab && *tab)
  {
    if (*tab == val)
      return 1;
    ++tab;
  }

  return 0;
}

static int create_and_bind(char* port)
{
  struct addrinfo hints;
  struct addrinfo* result;
  struct addrinfo* rp;
  int s;
  int sfd;

  memset(&hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
  hints.ai_flags = AI_PASSIVE;     /* All interfaces */

  if ((s = getaddrinfo(NULL, port, &hints, &result)) != 0)
  {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
    return -1;
  }

  for (rp = result; rp != NULL; rp = rp->ai_next)
  {
    if ((sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
      continue;

    if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
      break;

    close(sfd);
  }

  if (rp == NULL)
  {
    fprintf(stderr, "Could not bind\n");
    return -1;
  }

  freeaddrinfo(result);

  return sfd;
}

static int make_socket_non_blocking(int sfd)
{
  int flags;

  flags = fcntl(sfd, F_GETFL, 0);
  if (flags == -1)
  {
    perror("fcntl");
    return -1;
  }

  flags |= O_NONBLOCK;
  if (fcntl(sfd, F_SETFL, flags) == -1)
  {
    perror("fcntl");
    return -1;
  }

  return 0;
}

void add_socket_to_epoll(int sfd, int efd)
{
  struct epoll_event event;

  if (make_socket_non_blocking(sfd) == -1)
    abort();
  event.data.fd = sfd;
  event.events = EPOLLIN | EPOLLET;

  if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event) == -1)
  {
    perror("epoll_ctl");
    abort();
  }
}

int declare_socket(char* port, int efd)
{
  int sfd;

  if (((sfd = create_and_bind(port)) == -1))
    abort();

  add_socket_to_epoll(sfd, efd);

  if (listen(sfd, SOMAXCONN) == -1)
  {
    perror("listen");
    abort();
  }

  return sfd;
}

/* We have a notification on the listening socket, which
   means one or more incoming connections. */
static void handle_incoming_connection(int sfd, int efd)
{
  while (1)
  {
    struct sockaddr in_addr;
    socklen_t in_len;
    int infd;
    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

    in_len = sizeof (in_addr);
    infd = accept(sfd, &in_addr, &in_len);
    if (infd == -1)
    {
      /* We have processed all incoming
	 connections. */
      if ((errno != EAGAIN) && (errno != EWOULDBLOCK))
	perror("accept");
      return;
    }

    if (getnameinfo(&in_addr, in_len,
                    hbuf, sizeof hbuf,
                    sbuf, sizeof sbuf,
                    NI_NUMERICHOST | NI_NUMERICSERV) == 0)
    {
      printf("Accepted connection on descriptor %d "
	     "(host=%s, port=%s)\n", infd, hbuf, sbuf);
    }

    /* Make the incoming socket non-blocking and add it to the
       list of fds to monitor. */
    add_socket_to_epoll(infd, efd);
  }
}

static char* handle_command(const char* cmd)
{
  // Ici gérer l'ajout d'un thread

  printf("Cmd du client: %s\n", cmd);

  // Mettre le retour du serveur ou NULL si pas de retour.
  char* res = calloc(256, sizeof (char));
  strncpy(res, "Bonjour du serveur", 256);
  return res;
}

/* We have data on the fd waiting to be read. Read and
   display it. We must read whatever data is available
   completely, as we are running in edge-triggered mode
   and won't get a notification again for the same
   data. */
static int handle_session_socket_read(int fd)
{
  unsigned int size = 0;
  unsigned int limit = 512;
  char* cmd = calloc(limit, sizeof (char));
  char* res = NULL;
  int stop = 0;

  while (!stop)
  {
    ssize_t count;
    char buf[512];

    count = read(fd, buf, sizeof (buf));
    switch (count)
    {
      case -1:
        {
          /* If errno == EAGAIN, that means we have read all
             data. So go back to the main loop. */
          if (errno != EAGAIN)
          {
            perror("read");
            return 1;
          }
          stop = 1;
          break;
        }
      case 0:
	/* End of file. The remote has closed the
	   connection. */
	return 1;
      default:
	size += count;
	if (size > limit)
	{
	  limit *= 2;
	  cmd = realloc(cmd, limit * sizeof (char));
	}
	strncat(cmd, buf, count);
    }
  }
  res = handle_command(cmd);
  if (res)
  {
    write(fd, res, strlen(res));
    free(res);
  }
  free(cmd);

  return 0;
}

static void event_loop(struct epoll_event* events, int* sfds, int efd)
{
  while (1)
  {
    int i = 0;
    int n = epoll_wait(efd, events, MAXEVENTS, -1);
    for (i = 0; i < n; ++i)
    {
      if ((events[i].events & EPOLLERR) ||
	  (events[i].events & EPOLLHUP) ||
	  (!(events[i].events & EPOLLIN)))
      {
	/* An error has occured on this fd, or the socket is not
	   ready for reading (why were we notified then?) */
	fprintf(stderr, "epoll error\n");
	close(events[i].data.fd);
      }
      else if (is_in_tab(events[i].data.fd, sfds))
	handle_incoming_connection(events[i].data.fd, efd);
      else if (handle_session_socket_read(events[i].data.fd))
      {
      	printf("Closed connection on descriptor %d\n", events[i].data.fd);
      	/* Closing the descriptor will make epoll remove it
           from the set of descriptors which are monitored. */
      	close(events[i].data.fd); // /!\ Si utiilisation de thread, à déplacer ailleurs ! /!\ 
      }
    }
  }
}

int main(int argc, char* argv[])
{
  int sfd[65535] = {0};
  int efd;
  int i = 0;
  struct epoll_event* events;

  if (argc < 2)
  {
    fprintf(stderr, "Usage: %s port1 port2 etc...\n", argv[0]);
    exit(0);
  }

  if ((efd = epoll_create1(0)) == -1)
  {
    perror("epoll_create");
    abort();
  }

  for (i = 1; i < argc && i < 65535; ++i)
    sfd[i - 1] = declare_socket(argv[i], efd);

  events = calloc(MAXEVENTS, sizeof (struct epoll_event));

  event_loop(events, sfd, efd);

  free(events);
  for (i = 1; i < argc && i < 65535; ++i)
    close(sfd[i - 1]);

  return 0;
}


N'hésite pas si tu as des questions.

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
11 janv. 2014 à 04:56
J'ai lu ton truc, mais j'ai pas tout percuté.
La fatigue se fait sentir...
Je vais me pieuter. Je reprendrais ça demain,
et je relirais ton com' à tête reposé.

T'inquiète pas que si j'ai une question j'y manquerai pas. :)
Thanks en tout cas.
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 14/01/2014 à 14:05
Dans le code que je t'ai passé, introduire un thread proprement n'est pas chose aisé.

Le code passé est correct tant que le goulet d'étranglement est le nombre de clients, et non le cpu.
Je m'explique. Soit les tâches a effectuer par le serveur sont très petites, et alors il n'y a pas besoin de multi-threading (le goulet d'étranglement se situera alors dans la reception des nombreux clients), soit les tâches sont très longues, et là effectivement, sans thread, les clients vont attendre pour rien.

Exemple:
- Si tu fais un serveur qui reçoit et répond à des commandes simple (exemple: ls distant, chat avec phrases courtes, etc...): la solution sans thread est la plus rapide.
- Si tu fais un serveur qui reçoit des commandes qui peuvent être longues (exemple: un upload de fichier): la solution avec thread est nécessaire.

Disons que tu as besoin de la deuxième solution (qui est compatible avec les deux types de programme décrit ci-dessus). Il te faut alors des threads. Le souci, c'est que si tu crées des threads pour chaque client, ça va être l'horreur ! Si tu as 1000 clients par seconde, tu va créer et détruire 1000 threads, ce qui est très coûteux !
Pour régler ce problème, on utilise dans la "vraie vie", un "pool de threads". Un pool de threads contient un nombre fixe de threads, qui ne change jamais. On les crée une fois au démarrage de l'application, et on ne les détruit qu'à la fin de son exécution. Durant le déroulement du programme, on donne chacune des tâches reçues à l'un des threads du pool. Si aucun thread n'est disponible, on attend que l'un d'eux se libère. C'est qu'il y a des plus rapide et de plus propre.

Pour choisir un nombre de thread idéal, on prend généralement: "nb_thread == nb_coeur_cpu". Le nombre de coeur se récupère via: sysconf( _SC_NPROCESSORS_ONLN );

Pour réaliser le thread_pool, on utilise la technique suivante:
- On crée N threads que l'on met dans une queue.
- Chacun des threads tourne en arrière plan en boucle avec un while (1), et observe la pile de tâche.
- Dès qu'une tâche est disponible dans la pile, le premier des threads qui lit la pile à ce moment là, récupère la tâche, la retire de la pile, et l'exécute.
- Quand on récupère une commande d'un client, au lieu de l'exécuter, on l'ajoute à la pile de tâche.
- La récupération des tâches est synchronisée par un mutex (afin que le premier qui fait une opération sur la pile (ajout ou suppression de tâches), bloque les autres).

Petite subtilité: En vérité, les threads ne font pas tout à fait du while (1), sinon tu te doutes bien que le cpu ne ferait que ça. On utilise la technique "d'attente passive". C'est-à-dire que l'on utilise un "cond_mutex". Quand on arrive sur un "cond_mutex", on reste bloqué tant que celui-ci n'a pas été débloqué. C'est généralement un autre thread qui peut le débloquer. Ici ce que l'on fait, c'est que l'on bloque tous nos thread sur un "cond_mutex", qui vont attendre sans utiliser de cpu. A l'insertion d'une tâche dans la queue, on lance un signal qui débloque ce "cond_mutex". Les threads, une fois débloqués, vont faire leurs tâches (s'ils en ont). Puis, via le while (1) au dessus, vont revenir sur le cond_mutex, qui sera de nouveaux bloqué.

Je t'ai joint un client serveur complet pour étayer mes dires (le découpage des fichiers est horrible, vu que j'ai tout mis dans un seul fichier, je te laisse le découper proprement).

Client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MAXRCVLEN 500
#define PORTNUM 2343

int send_cmd(int mysocket, const char* cmd)
{
  char buffer[MAXRCVLEN + 1]; /* +1 so we can add null terminator */
  int len;

  send(mysocket, cmd, strlen(cmd), 0);
  len = recv(mysocket, buffer, MAXRCVLEN, 0);
  if (len > 0)
  {
    /* We have to null terminate the received data ourselves */
    buffer[len] = '\0';
    printf("Received %s (%d bytes).\n", buffer, len);
    return 1;
  }

  if (len < 0)
    perror("recv");
  else
    printf("Server closed connection\n");

  return 0;
}

int main(int argc, char* argv[])
{
  int mysocket;
  int stop = 0;
  struct sockaddr_in dest;

  if (argc < 2)
  {
    printf("Usage: %s [port]\n", argv[0]);
    return 1;
  }

  mysocket = socket(AF_INET, SOCK_STREAM, 0);
  memset(&dest, 0, sizeof(dest));                /* zero the struct */
  dest.sin_family = AF_INET;
  dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* set destination IP number - localhost, 127.0.0.1*/
  dest.sin_port = htons(atoi(argv[1]));                /* set destination port number */

  if (connect(mysocket, (struct sockaddr *)&dest, sizeof (struct sockaddr)))
  {
    perror("connect error!");
    exit(1);
  }

  send_cmd(mysocket, "Coucou c'est moi le client");

  while (!stop)
  {
    char buff[128] = {0};
    printf("$ ");
    scanf("%s", buff);
    if (strcmp(buff, "quit") == 0)
      stop = 1;
    else
      send_cmd(mysocket, buff);
  }

  close(mysocket);

  return 0;
}


Serveur:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <assert.h>

typedef void (*process_func)(void* arg);

typedef struct worker
{
  process_func process;
  void* arg;
  struct worker* next;
} thread_worker;

typedef struct param
{
  int* epoll_fd;
  int* accept_fd;
  int* maxfd;
  struct epoll_event* ev;
} thread_param;

typedef struct
{
  pthread_mutex_t mutex;
  pthread_cond_t ready;
  pthread_t* thread;

  thread_worker* queue;

  int shutdown;
  int max_thread;
  int queue_size;
} thread_pool;

/*
  Assume that tab finish by 0
*/
static int is_in_tab(int val, int* tab)
{
  while (tab && *tab)
  {
    if (*tab == val)
      return 1;
    ++tab;
  }

  return 0;
}

static void* pool_thread_worker(void* arg)
{
  thread_pool* pool = arg;
  printf("[Debug] Starting thread 0x%x\n", (unsigned int) pthread_self());
  while (1)
  {
    pthread_mutex_lock(&(pool->mutex));
    while (pool->queue_size == 0 && !pool->shutdown)
    {
      printf("[Debug] Thread 0x%x is waiting\n", (unsigned int) pthread_self());
      pthread_cond_wait(&(pool->ready), &(pool->mutex));
    }

    if (pool->shutdown)
    {
      pthread_mutex_unlock(&(pool->mutex));
      printf("[Debug] Thread 0x%x will exit\n", (unsigned int) pthread_self());
      pthread_exit(NULL);
    }
    printf("[Debug] Thread 0x%x is starting to work\n", (unsigned int) pthread_self());

    assert(pool->queue_size != 0);
    assert(pool->queue != NULL);

    --pool->queue_size;
    thread_worker* worker = pool->queue;
    pool->queue = worker->next;
    pthread_mutex_unlock(&(pool->mutex));
    printf("[Debug] Start process...\n");
    (*(worker->process))(worker->arg);
    free(worker);
  }

  pthread_exit(NULL);
}

static thread_pool* pool_init(int max_thread)
{
  int i = 0;
  thread_pool* pool = malloc(1 * sizeof (thread_pool));
  pthread_mutex_init(&(pool->mutex), NULL);
  pthread_cond_init(&(pool->ready), NULL);
  pool->queue = NULL;
  pool->max_thread = max_thread;
  pool->queue_size = 0;
  pool->shutdown = 0;
  pool->thread = malloc(max_thread * sizeof (pthread_t));
  for (i = 0; i < max_thread; ++i)
    pthread_create(&(pool->thread[i]), NULL, pool_thread_worker, pool);

  return pool;
}

static void pool_destroy(thread_pool* pool)
{
  int i;
  thread_worker* head = NULL;

  if (pool->shutdown)
    return;
  pool->shutdown = 1;

  pthread_cond_broadcast(&(pool->ready));
  for (i = 0; i < pool->max_thread; ++i)
    pthread_join (pool->thread[i], NULL);
  free (pool->thread);

  while (pool->queue != NULL)
  {
    head = pool->queue;
    pool->queue = pool->queue->next;
    free(head);
  }

  pthread_mutex_destroy(&(pool->mutex));
  pthread_cond_destroy(&(pool->ready));
  free(pool);
}

void pool_add_worker(thread_pool* pool, process_func process, void *arg)
{

  thread_worker* worker = malloc(1 * sizeof (thread_worker));
  worker->process = process;
  worker->arg = arg;
  worker->next = NULL;
  pthread_mutex_lock(&(pool->mutex));

  thread_worker* queue = pool->queue;
  if (queue == NULL)
    pool->queue = worker;
  else
  {
    while (queue->next != NULL)
      queue = queue->next;
    queue->next = worker;
  }

  assert(pool->queue != NULL);
  ++pool->queue_size;
  printf("[Debug] Queue size: %i\n", pool->queue_size);
  pthread_mutex_unlock(&(pool->mutex));
  pthread_cond_signal(&(pool->ready));
}

static int create_and_bind(char* port)
{
  struct addrinfo hints;
  struct addrinfo* result;
  struct addrinfo* rp;
  int s;
  int sfd;

  memset(&hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
  hints.ai_flags = AI_PASSIVE;     /* All interfaces */

  if ((s = getaddrinfo(NULL, port, &hints, &result)) != 0)
  {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
    return -1;
  }

  for (rp = result; rp != NULL; rp = rp->ai_next)
  {
    if ((sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
      continue;

    if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
      break;

    close(sfd);
  }

  if (rp == NULL)
  {
    fprintf(stderr, "Could not bind\n");
    return -1;
  }

  freeaddrinfo(result);

  return sfd;
}

static int make_socket_non_blocking(int sfd)
{
  int flags;

  flags = fcntl(sfd, F_GETFL, 0);
  if (flags == -1)
  {
    perror("fcntl");
    return -1;
  }

  flags |= O_NONBLOCK;
  if (fcntl(sfd, F_SETFL, flags) == -1)
  {
    perror("fcntl");
    return -1;
  }

  return 0;
}

void add_socket_to_epoll(int sfd, int efd)
{
  struct epoll_event event;

  if (make_socket_non_blocking(sfd) == -1)
    abort();
  event.data.fd = sfd;
  event.events = EPOLLIN | EPOLLET;

  if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event) == -1)
  {
    perror("epoll_ctl");
    abort();
  }
}

int declare_socket(char* port, int efd)
{
  int sfd;

  if (((sfd = create_and_bind(port)) == -1))
    abort();

  add_socket_to_epoll(sfd, efd);

  if (listen(sfd, SOMAXCONN) == -1)
  {
    perror("listen");
    abort();
  }

  return sfd;
}

/* We have a notification on the listening socket, which
   means one or more incoming connections. */
static void handle_incoming_connection(int sfd, int efd)
{
  while (1)
  {
    struct sockaddr in_addr;
    socklen_t in_len;
    int infd;
    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

    in_len = sizeof (in_addr);
    infd = accept(sfd, &in_addr, &in_len);
    if (infd == -1)
    {
      /* We have processed all incoming
  connections. */
      if ((errno != EAGAIN) && (errno != EWOULDBLOCK))
 perror("accept");
      return;
    }

    if (getnameinfo(&in_addr, in_len,
                    hbuf, sizeof hbuf,
                    sbuf, sizeof sbuf,
                    NI_NUMERICHOST | NI_NUMERICSERV) == 0)
    {
      printf("Accepted connection on descriptor %d "
      "(host=%s, port=%s)\n", infd, hbuf, sbuf);
    }

    /* Make the incoming socket non-blocking and add it to the
       list of fds to monitor. */
    add_socket_to_epoll(infd, efd);
  }
}

static char* handle_command(const char* cmd)
{
  printf("Cmd du client: %s\n", cmd);

  // Mettre le retour du serveur ou NULL si pas de retour.
  char* res = calloc(256, sizeof (char));
  strncpy(res, "Bonjour du serveur", 256);
  return res;
}

/* We have data on the fd waiting to be read. Read and
   display it. We must read whatever data is available
   completely, as we are running in edge-triggered mode
   and won't get a notification again for the same
   data. */
static void handle_session_socket_read(void* arg)
{
  thread_param* param = arg;
  unsigned int size = 0;
  unsigned int limit = 512;
  char* cmd = calloc(limit, sizeof (char));
  char* res = NULL;
  int stop = 0;

  while (!stop)
  {
    ssize_t count;
    char buf[512];

    count = read(*param->accept_fd, buf, sizeof(buf));
    switch (count)
    {
      case -1:
        {
          /* If errno == EAGAIN, that means we have read all
             data. So go back to the main loop. */
          if (errno != EAGAIN)
          {
            perror("read");
            return;
          }
          stop = 1;
          break;
        }
      case 0:
 /* End of file. The remote has closed the
    connection. */
        printf("Closed connection on descriptor %d\n", *param->accept_fd);
       close(*param->accept_fd);
        epoll_ctl(*(param->epoll_fd), EPOLL_CTL_DEL, *(param->accept_fd), param->ev);
        --(*param->maxfd);
 return;
      default:
 size += count;
 if (size > limit)
 {
   limit *= 2;
   cmd = realloc(cmd, limit * sizeof (char));
 }
 strncat(cmd, buf, count);
    }
  }
  res = handle_command(cmd);
  if (res)
  {
    write(*param->accept_fd, res, strlen(res));
    free(res);
  }
  free(cmd);
}

static void event_loop(thread_pool* pool, struct epoll_event* events, int* sfds, int efd, int maxfd)
{
  while (1)
  {
    int i = 0;
    int n = epoll_wait(efd, events, maxfd, -1);
    for (i = 0; i < n; ++i)
    {
      if ((events[i].events & EPOLLERR) ||
   (events[i].events & EPOLLHUP) ||
   (!(events[i].events & EPOLLIN)))
      {
 /* An error has occured on this fd, or the socket is not
    ready for reading (why were we notified then?) */
 fprintf(stderr, "epoll error\n");
 close(events[i].data.fd);
      }
      else if (is_in_tab(events[i].data.fd, sfds))
 handle_incoming_connection(events[i].data.fd, efd);
      else
      {
        thread_param* param = malloc(1 * sizeof (thread_param));
        param->epoll_fd = &efd;
        param->accept_fd = &events[i].data.fd;
        param->ev = &events[i];
        param->maxfd = &maxfd;
        pool_add_worker(pool, &handle_session_socket_read, param);
      }
    }
  }
}

int main(int argc, char* argv[])
{
  int maxfd = 10;
  int sfd[65535] = {0};
  int efd;
  int i = 0;
  struct epoll_event* events;

  if (argc < 2)
  {
    fprintf(stderr, "Usage: %s port1 port2 etc...\n", argv[0]);
    exit(0);
  }

  thread_pool* pool = pool_init(sysconf(_SC_NPROCESSORS_ONLN ));

  if ((efd = epoll_create1(0)) == -1)
  {
    perror("epoll_create");
    abort();
  }

  for (i = 1; i < argc && i < 65535; ++i)
    sfd[i - 1] = declare_socket(argv[i], efd);

  events = calloc(maxfd, sizeof (struct epoll_event));
  event_loop(pool, events, sfd, efd, maxfd);

  pool_destroy(pool);
  free(events);
  for (i = 1; i < argc && i < 65535; ++i)
    close(sfd[i - 1]);

  return 0;
}


N'hésite pas si tu as des questions.

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
11 janv. 2014 à 16:33
Ok merci,
ça me fait plaisir d'apprendre plein de chose en t'as compagnie cptpingu. xD

Je vais analyser et digérer ce que tu m'as donné pour commencer.
Et je reviendrai si besoin.
En tout cas, rien n'est obscure dans ce que j'ai lu, donc ça devrait le faire. Parce qu'en effet, c'est vrai que d'actionner des threads par une pile et des mutex conditionnels n'est pas une mauvaise idée...
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
14 janv. 2014 à 23:21
/*
SERVEUR PROXY-CACHE
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include "constante.h"


#define NB_MAX_COEURS_PROC 8
#define NB_MAX_CLIENTS_SIMULTANNES 10
#define TAILLE_MAX_PILE_CLIENTS NB_MAX_CLIENTS_SIMULTANNES
#define TAILLE_REQUETE 1024


typedef struct PileClients
{
int i;
int pile[TAILLE_MAX_PILE_CLIENTS];
}PileClients;

//var globales
PileClients pile;




int lectureSockComClient(int sock_com, char *requeteClient)
{
//----------------------------------------------------------------------------------
// Phase d'échange - Lecture Sock
//----------------------------------------------------------------------------------
if(read(sock_com, requeteClient, strlen(requeteClient)) == -1)
{
perror(">> Erreur : Probleme de lecture de la socket.\n");
return 0;
}
return 1;
}

int ecritureSockComClient(int sock_com, char *reponseClient)
{
//----------------------------------------------------------------------------------
// Phase d'échange - Ecriture Sock
//----------------------------------------------------------------------------------
if(write(sock_com, reponseClient, strlen(reponseClient)) == -1)
{
perror(">> Erreur : Il y a un probleme dans l'envoi de la réponse au client\n");
return 0;
}
return 1;
}

int traitementRequeteClient(char *requeteClient, char *reponseClient)
{
//TO DO
return 1;
}



void *thread_GestionClient(void *arg)
{
while(1)
{
//récup des info dans la pile de tâche quand le signal est reçu
pthread_mutex_lock(&mutex1);
pthread_cond_wait(&cond1, &mutex1);//attend le signal - relâche le verrou jusqu'a la reception d'un signal
int sock_com = pile.pile[pile.i];
pile.i--;
pthread_mutex_unlock(&mutex1);

//réception et traitement de la requête client
//lecture de la requete
char requeteClient[TAILLE_REQUETE] = "";
if(lectureSockComClient(sock_com, requeteClient) == 1)
{
printf("%s\n", requeteClient);
//traitement de la requete si elle a été lu correctement
char reponseClient[TAILLE_REQUETE] = "";
if(traitementRequeteClient(requeteClient, reponseClient) == 1)
{
strcpy(reponseClient, "salut");//pour test
//on envoit la requete réponse au client
if(ecritureSockComClient(sock_com, reponseClient) == 1)
printf("Réponse pour le client :\n%s\n", reponseClient);
}
}
//on ferme la socket de comunication
close((int) sock_com);
}
}


int main()
{
printf("### Début prgm ServeurProxy ###\n\n");

//initialisation struct PileClients pour les threads
pile.i = -1;

pthread_t thread_clients[NB_MAX_COEURS_PROC];
pthread_cond_init(&cond1, NULL);

//création des threads qui s'occuperont de gérer les requêtes des clients
int i;
for(i=0 ; i<sysconf(_SC_NPROCESSORS_ONLN) ; i++)//sysconf(_SC_NPROCESSORS_ONLN) retourne le nombre de coeurs dispo et actif sur le système
if(pthread_create(&thread_clients[i], NULL, thread_GestionClient, (void*) i) != 0)//si la création a foiré : renvoit != 0
perror(">> Erreur de creation du thread\n");


//écoute des connexions TCP sur le port 80
while(1)
{
int sock;
struct sockaddr_in adresse;
u_short port = 80;

//ouverture d'une socket
sock = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM = TCP / SOCK_DGRAM = UDP
if(sock < 0)
perror(">> Erreur d'ouverture de la socket");
else
{
//affectation de l'adresse a la structure
adresse.sin_family = AF_INET;//AF_INET = internet (TCP/IP)
adresse.sin_port = htons((u_short)port);//La fonction htonl() convertit l'entier non signé hostlong depuis l'ordre des octets de l'hôte vers celui du réseau (which is big-endian).
adresse.sin_addr.s_addr = htonl(INADDR_ANY);// = prêt à recevoir de n'importe quel clients.

//pour permettre de réutiliser une socket existante
int options = -1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &options, sizeof (options));

//association de la socket a l'adresse local
if(bind(sock, (struct sockaddr *) &adresse, sizeof(adresse)) != 0)
perror(">> Erreur d'association");
else
{
//----------------------------------------------------------------------------------
// Phase de connexion
//----------------------------------------------------------------------------------

//attente de connexions client
printf("Serveur en attente d'une connexion ...\n\n");
if(listen(sock, NB_MAX_CLIENTS_SIMULTANNES) != 0)
perror(">> Erreur de la fonction Listen");
else
{
while(1)
{
//Acceptation de la connexion
int sock_com = accept(sock, (struct sockaddr *) 0, (int *) 0);
if(sock_com == -1)
perror(">> Erreur de la fct Accept");
else
{
printf("...CONNEXION ETABLIE...\n\n");


//on ajoute le nouveau client à la pile de tâche
pthread_mutex_lock(&mutex1);
pile.i++;
pile.pile[pile.i] = sock_com;
pthread_mutex_unlock(&mutex1);

pthread_cond_signal(&cond1);//envoie le signal mais ne reveil qu'un seul wait();

}
}
}
}
}
}
}


Voilà, voici ce que j'ai actuellement.
C'est fonctionnel, mais c'est sûrement très brouillon vu que j'ai encore laissé pas mal de bout de code qui me permettent de vérifier l'état de fonctionnement de mon soft pendant les phases de test.

Donc normalement j'ai assimiler tout ce que tu m'as dit cptpingu.
Le multithreading semble correcte, et d'ailleurs ça marche même plutôt bien le principe de la pile de tâche (que j'ai matérialisé par un tableau avec son index dans une structure déclarée en globale et dont l'accès est protégé par les mutex conditionnel).

Pour le moment mon code gère le multithreading et gère la communication avec le client. Il me reste donc encore à faire tout le traitement de la requête reçue du client (vérification diverses, gestion du cache, recherche de la page désirée, etc...).


Sauf que j'aurai une question : comment aller récupérer la page que le client désire depuis mon proxy ?

(je suis sûr que c'est pas compliqué et qu'il faut calquer avec le principe d'un client de base...)
Mais j'aimerais juste comprendre comment cela se déroule.

Si on prend l'exemple de la demande de la page "www.google.fr/". Doit-on établir une connexion sur le serveur de google pour lui demander avec une requête GET ? Ca voudrait dire qu'il faut décortiquer la requête reçue par le client pour trouver où se co...

J'ai lu tout ça :
http://fr.openclassrooms.com/informatique/cours/les-requetes-http/
Donc je sais à peu près comment faire, mais ça reste un poil flou...

Actuellement j'ai déjà fait un client tout basique histoire de tester. J'arrive à me co sur un serveur distant (à coup de gethostbyname()). Mais je comprends pas encore les étapes qui régissent la demande d'une page...

Si quelqu'un peut m'éclairer ..?
Merci d'avance.
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 15/01/2014 à 16:18
Quelques critiques de ton code:
- Évite les variables globales. Ici, on peut aisément s'en passer.
- Vu que "mutex" et "cond" ont pour rôle de protéger les données de la structure pile, tu peux tout à fait inclure ces variables dans la structure.
- Le nommage n'est pas terrible dans la structure "pile": Par exemple, "i" n'est pas très explicite ("nbCurrentClient" serait peut être plus judicieux).
- C'est dommage de ne gérer que des messages à taille limitée (mais plus facile je te l'accorde).
- Le découpage de la fonction main n'est pas terrible (trop longue).
- Nettoie tes headers, il y en a plein qui ne servent pas.
- Le "franglais" n'est pas terrible :p. Code en anglais entièrement.
- Soit tu utilises "_SC_NPROCESSORS_ONLN", soit "NB_MAX_COEURS_PROC", mais pas les deux. Ici tu as un bug potentiel, puisque si le nombre de coeur réels dépasse 8, tu auras de gros soucis :p.

Sauf que j'aurai une question : comment aller récupérer la page que le client désire depuis mon proxy ?
Ton serveur possède des clients, mais rien l'empêche d'être lui même client d'un autre serveur. Pour cela, tu va devoir implémenter un code semblable à un client au sein de ton serveur.
Dans la théorie, un client t'envoie une requête pour une addresse donnée. Tu la récupère avec ton serveur. Ton serveur est client d'un serveur http distant donné par l'url du client. Ton serveur envoie la requête lui-même, et récupère la réponse. Il la traite, et la renvoie au client.

Ca voudrait dire qu'il faut décortiquer la requête reçue par le client pour trouver où se co...
Si tu veux gérer de l'HTTP à la main, oui il va falloir décortiquer les requêtes et gérer le protocole entièrement toi même !
Pour cela, il va falloir que tu "discutes" avec le serveur web (google par exemple), en utilisant les commandes HTTP. Tu peux voir un petit exemple ici:
http://coding.debuntu.org/c-linux-socket-programming-tcp-simple-http-client

Détail important: quelle version du protocole dois-tu utiliser ? 1.0, 1.1 ?

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
theGrimReaper Messages postés 25 Date d'inscription dimanche 31 octobre 2010 Statut Membre Dernière intervention 18 janvier 2014
18 janv. 2014 à 20:59
Merci encore une fois cptpingu,
j'ai pu rendre mon projet mais pas entièrement fini. Il me manquait l'implémentation du cache. (manque de temps)

Mais je vais terminer ce projet dès que j'aurai au peu de temps à y consacrer car c'est intéressant.

Pour les critiques que tu as fait de mon code,
la plupart je le savais déjà. Mais c'était un brouillon que je t'ai soumis, avec encore tous mes tests. Mais c'est pas une excuse non plus, parce qu'il t'ait très facile de simplement me dire : "pourquoi ne codes tu pas directement de manière propre ?"
C'est vrai...

En tout cas merci.
Sujet résolut.
0
StayCrunchy Messages postés 43 Date d'inscription mercredi 24 novembre 2010 Statut Membre Dernière intervention 26 février 2014
Modifié par StayCrunchy le 26/02/2014 à 11:59
Bonjour,
En premier lieu, merci à theGrimReaper de m'avoir grandement maché le travail avec toutes les questions posées et Cptpingu pour les réponses !

Je me permets de venir poster une petite question à la suite de cette discussion.
J'ai créé un petit serveur du style while() + accept + pthread_create.
Pour la faire rapide, je créé un thread pour chaque client dans le but de transferer un fichier aux clients connectés.

Ma question porte sur la réaction de mon programme lorsqu'un client se déconnecte de manière imprévue (perte wifi, cable eth débranché, coupure de courant, bref vous avez capté lol).

Il arrive lors d'une déconnexion que le thread reste bloqué.
J'ai opté pour une solution (qui risque d'en faire sourire plus d'un) : l'utilisation de "pthread_cancel()" pour forcer la déconnexion du serveur au client et fermer le thread.
(il va de soit que les dispositions ont été prises pour clore les sockets des clients stoppés par pthread_cancel())

Si à ce stade vous n'avez pas encore souri, je suis rassuré mais attendez la suite..
Autant vous prévenir que pour gerer les threads clients, j'ai créé depuis le main() un thread dédié au contrôle de tous les autres threads qui seront créés par la connnexion des clients.
Ce thread vérifie par le biais d'un "gettimeofday" à quand remonte le dernier signe de vie du thread en gros.
(gettimeofday, pthread_cancel, pthread_join à la suite)

Bref je me rends compte que si je savais mieux gerer les déconnexions ou l'absence de réponse d'un client, mon programme serait sans doute moins compliqué..
Je précise que tout fonctionne correctement, le problème des clients deconnectés est reglé.
Mon probleme est donc ma solution que je trouve un peu bancale.
Y a-t-il plus propre pour éviter qu'un thread attende une réponse qui ne viendra peut-etre jamais ?

J'espère avoir réussi à être clair ...

ps : peut-etre est-il utile de préciser que j'utilise les fonction send() et recv() pour les communications.
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
26 févr. 2014 à 12:20
Bonjour.

Alors il faut bien différencier deux problèmes bien distincts:
- Le client se déconnecte (volontairement ou non). Tu es alors immédiatemment prévenu, et la déconnexion est naturelle. Je t'invite à tester la version finale que j'ai posté sur ce thread, et à faire un CTRL + C sur le client. Tu verras sur le serveur que la déconnexion est visible instantannément. Si ton programme ne réagit pas comme cela, alors tu dois avoir un souci quelque part. Je t'invite à comparer ton code avec le mien.
- Le client ne répond pas. Ça peut être du à un problème réseau, ou au fait que le client est parti en vrille (ou est en train de faire un gros calcul, et ne peut momentanément pas répondre). On est ici dans le cas où il est toujours connecté, mais ne répond pas. Pour gérer cela, tu n'as pas le choix. Il faut "pinger" le client à intervals réguliers (qui doit alors être serveur aussi), et s'il ne répond pas au bout d'un temps défini, on le déconnecte (ou bien faire comme tu le fais, allouer un temps défini au client pour effectuer son opération). Normalement une fermeture du socket est suffisant, et devrait débloquer ton thread, qui ne sera plus en attente. Si tu as besoin de "cancel" un thread, c'est qu'il se débloque mal. Il faut en chercher la raison (C'est facile à dire, mais je ne peux rien te conseiller de plus).

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
StayCrunchy Messages postés 43 Date d'inscription mercredi 24 novembre 2010 Statut Membre Dernière intervention 26 février 2014
26 févr. 2014 à 12:29
Ok, je vais regarder plus en détails ce que tu avais déjà posté. Compte sur moi pour redonner de mes nouvelles en cas de problème ! ;)
Merci pour la réponse rapide !
0
Coucou, je déterre ce topic car très interressant et en français =)
Je débute un peu en epoll et j'essaie de comprendre un peu tout ça...
Alors, si tu existe toujours cptpingu, j'ai une tite question pour toi...
Lorsque à la fin tu fais un "write", on est en non bloquant non? ne devrait-on pas en plus vérifier si l'envoie s'est passé en entier ? et peut être réessayer plus tard? ou le write reste bloquant lui? Rhhaaa dsl si ça parait idiot mais je rame^ ^
Merci @ toute personne qui pourrait me répondre ;)
0
Rejoignez-nous