Sockets asynchrones en C sous Windows - C - Programmation
Marsh Posté le 23-03-2009 à 22:15:02
Une socket n'est ni synchrone ni asynchrone, c'est l'utilisation que tu en fais qui va l'être ou non.
Pour une utilisation asynchrone (c'est à dire que tu ne sais pas quand tu reçois des données), pour éviter de l'attente active ou du polling sur la réception, l'idéal est d'utiliser la fonction select qui te permettra de détecter que des données sont reçues pour pouvoir les lire.
Généralement, j'ai un thread pour l'envoi, et un thread pour la réception, mais ce n'est pas obligatoire.
Ici une documentation qui explique bien comment utiliser ces fonctions : http://mapage.noos.fr/emdel/reseaux.htm
Marsh Posté le 24-03-2009 à 02:40:45
Salut xilebo, merci pour la repense. J'ai déjà vu ce lien (qui est bien, au passage), l'utilisation de sockets que je veux faire c'est en fait pour faire un serveur qui peut supporté la connexion de plusieurs client, donc si je fait comme expliqué sur ce lien en faisant en sorte de créer un thread pour chaque client connecté, ça ne va pas le faire (beaucoup trop de threads) d'où mon envi d'une utilisation asynchone pour éviter les threads.
Je peut donc faire tout ça grâce à select() ? (c'est seulement ça qui caractérise les sockets asynchrones) ?
Marsh Posté le 24-03-2009 à 16:38:44
xilebo a écrit : Une socket n\'est ni synchrone ni asynchrone, c\'est l\'utilisation que tu en fais qui va l\'être ou non. |
N\'importe quoi.
C\'est expliqué en long et en large dans MSDN (avec des exemples de \"asynchronous sockets\" !)
Marsh Posté le 24-03-2009 à 17:31:15
Pat333 a écrit : |
Ton message ne sert à rien, comme tes 3 seuls autres messages sur ce forum.
Si tu n'es pas d'accord, la moindre des choses, c'est d'expliquer pourquoi, et pas citer une phrase passe partout comme tu viens de le faire.
Je persiste : une socket n'est ni synchrone, ni asynchrone, c'est l'utilisation que tu vas en faire qui va l'être. Je précise : généralement, une utilisation synchrone d'une socket requiert une configuration en mode bloquant (mais pas forcément), alors qu'une utilisation asynchrone requiert une socket configurée en mode non bloquant (mais pas forcément non plus).
En mode bloquant, ton recv ne retournera pas tant que tu n'auras pas recu de données ou une erreur, en mode non bloquant, ta fonction recv retourne toujours, données recues ou pas. C'est pour cela qu'il faut utiliser la fonction select pour eviter l'attente active ou le polling.
Le mode asynchrone suppose que tu n'as aucune synchronisation entre les données émises (send) et les données recues (recv). Généralement, on a un thread séparé pour chacune des tâches mais ce n'est pas obligatoire.
Marsh Posté le 24-03-2009 à 18:28:06
Pour un serveur qui permet de faire communiquer plusieurs clients (un chat instantané), vous me conseillez une utilisation synchrone (en mode bloquant) ou bien une utilisation asynchrone ?
J'ai commencé à réaliser ça en mode synchrone en utilisant les threads au niveau du serveur (je crée un thread pour chaque client connecté), mais je trouve que comme ça il y aura beaucoup trop de threads lancé simultanément au niveau du serveur, et aussi y aura le problème de communication entre threads (quand on veux par exemple qu'un client envoi au autres clients connectés).
Pour réglé un peut ses problèmes, on m'a dirigé vers select() donc: sockets non bloquants + threads + select().
Mais je me demande si avec tout ça, ça ne sera pas plus facile d'utiliser des sockets en mode non bloquant (asynchrone); Il parais qu'une utilisation asynchrone est plus difficile qu'une utilisation synchrone + threads.
Vous en dite quoi ?
En fait quand j'ai voulu utiliser les sockets en mode non bloquant j'ai suivi le schéma de serveur-multi-clients montré sur ce lien http://mapage.noos.fr/emdel/reseaux.htm , mais comment faire ensuite pour diminuer de nombre de threads grâce select() ? Parce qu'un thread par client connecté, c'est beaucoup..
Dois-je créer un thread par client connecté, et dans chaque thread y aura la fonction select() qui va suspendre ce thread quand il n y a pas de donnée à recevoir ... ?
Marsh Posté le 24-03-2009 à 18:51:11
La fonction select accepte en paramètre des ensembles de descripteur de fichiers ( sockets par exemple). Tu peux gérer toutes tes connexions dans un seul thread de réception, en appelant la fonction select avec l'ensemble de tes sockets. la fonction select retournera que des données sont recues sur au moins une connexion (si tu dois recevoir des données), après il te suffira de tester sur quelle connexion sont recues les données pour effectuer ton traitement.
man select sur google.fr
ou à nouveau le lien que je t'ai donné ci-dessus //mapage.noos.fr/emdel/reseaux.htm#select
Marsh Posté le 24-03-2009 à 19:32:55
xilebo a écrit : La fonction select accepte en paramètre des ensembles de descripteur de fichiers ( sockets par exemple). Tu peux gérer toutes tes connexions dans un seul thread de réception, en appelant la fonction select avec l'ensemble de tes sockets. la fonction select retournera que des données sont recues sur au moins une connexion (si tu dois recevoir des données), après il te suffira de tester sur quelle connexion sont recues les données pour effectuer ton traitement. man select sur google.fr |
Mais le problème c'est que je ne peut pas appelé select() avec l'ensemble de mes sockets puisqu'il ne sont pas connu au début ! C'est la fonction accept() qui retourne le socket client lors de la connexion d'un client. Je ne peut donc pas connaitre à l'avance tout les sockets clients qu'il y aura pour les passer à select(); Et encore moins tester sur quelle connexion sont reçues les données pour effectuer mon traitement; de plus que la fonction accept() est bloquante.
Je ne vois donc pas comment faire pour gérer mes connexions dans un seul thread. A moins que select() retourne une valeur précise quand il y a une demande de connexion.. ce qui n'est pas le cas à ce que je vois.
Le schéma actuel (sans select) est le suivant:
Début Boucle:
attendre une connexion: accept() // ça me retourne le socket client qui me permettra de communiquer.
créer un thread avec le socket client
Fin Boucle.
à quel niveau j'aurai à utiliser select() pour n'avoir qu'un seul thread à gérer ?
Marsh Posté le 24-03-2009 à 20:03:49
je n'ai pas dit un seul thread, j'ai juste dit un thread pour les réceptions.
Tu peux avoir un thread pour l'envoi, un thread pour la réception (ou bien un thread pour les 2), et un thread d'écoute pour accepter les connexions. Tu n'as qu'à stocker tes sockets dans une liste commune aux 2 threads, mais attention aux accès concurrentiels (mutex pour protéger les écritures).
Ex (j'ai pas vérifié l'intégrité) :
Thread d'écoute :
Debut boucle
Attendre Accept
Si connexion acceptée
Lock
ajouter connexion dans liste
Unlock
Fin boucle.
Thread d'envoi et reception
Debut boucle
Lock
ajouter liste de descripteur dans structure prevue a cet effet (FDSET)
Unlock
select sur ta liste
si données recues
traiter...
fin si
Fin Boucle
Marsh Posté le 25-03-2009 à 19:28:08
Ok. Est ce que le mieux serai que la liste contenant les sockets client (valeurs int) soit une liste chainée, ou un tableau d'entier, ou ... ?
Sachant que je dois tester si des données ont été reçu sur la liste des sockets, on la parcourant; et qu'il faut retiré la valeur correspondante au socket client correspondant au client qui se déconnecte (qui envoi un message pour dire qu'il veux se déconnecter ou quand recv() renvoi une valeur =<0 avec ce socket client).
Marsh Posté le 26-03-2009 à 09:12:54
tomap a écrit : Ok. Est ce que le mieux serai que la liste contenant les sockets client (valeurs int) soit une liste chainée, ou un tableau d'entier, ou ... ? |
Oui, c'est une technique courante.
Marsh Posté le 26-03-2009 à 20:11:36
n'oublie pas de protéger l'insertion et la suppression d'une socket dans la liste par des mutex.
Et dans tous les cas, il faut toujours tester la valeur de retour d'un appel système. Attention, sous linux (windows je sais pas), recv peut renvoyer -1 sans avoir de socket fermée, il faut egalement tester errno, car tu peux avoir été interrompu par un signal (errno == EINTR).
Marsh Posté le 26-03-2009 à 20:26:05
J'ai une liste shared_list pour contenir les sockets clients connectés.
Dans le thread principale main() j'ai une boucle qui attend les connexion et les ajoute dans la liste. Et j'ai un thread dans lequel j'ai utilisé select() pour attendre de recevoir des données. Mais quand je lance le serveur (sans lancé aucun client au début) select() me retourne -1, et ça m'affiche donc "error" en boucle.
Comment faire donc ? au début c'est normale qu'il n y ai aucun socket client dans la liste.
Code :
|
Marsh Posté le 26-03-2009 à 20:35:46
Sous windows, il faut initialiser la pile reseau en appelant WSAStartup() et WSACleanUup()
Utilisation : http://msdn.microsoft.com/en-us/li [...] S.85).aspx
Marsh Posté le 26-03-2009 à 20:57:55
En fait j'utilise déjà WSAStartup() et WSACleanup() mais il y a quand même le problème que j'ai cité plus haut avec select() (elle me retoun -1)
Marsh Posté le 26-03-2009 à 21:17:47
Dans ce cas, teste l'erreur avec WSAGetLastError() http://msdn.microsoft.com/en-us/li [...] S.85).aspx
Marsh Posté le 26-03-2009 à 21:53:08
WSAGetLastError() me retourne la valeur 10022
Citation : WSAEINVAL 10022 : Invalid argument. |
ça dois être parce que j'appelle FD_SET() avec le 1er argument qui est naturellement invalide vu que la liste est initialement vide (tete = NULL).
Donc pour régler ça, dans ma boucle je teste si shared_list.tete != NULL, mais bon, ça va bouffer du CPU en bouclant quand il n y a aucun client connecté (liste vide càd tete=NULL) ...
Code :
|
Une autre question: est ce qu'il faut à chaque fois que je veux "lire" une valeur de la liste que j'utilise un mutex (lock/unlock) ? Ou alors ce n'est naicessaire que lors de l'écriture sur la liste (lors d'ajout/suppression...) ?
EDIT:
Tient je remarque un truc bizarre. Je lance le serveur, puis je lance deux clients (client_1 puis client_2); lorsque client_1 envoi un message au serveur il reçoit la repense (normale), mais après lorsque client_2 envoi un message il ne reçoit pas la repense (il reste bloqué sur recv), mais il suffit que client_1 envoi un autre message au serveur pour que client_2 reçoit la repense à son 1er message ! Je ne vois pas vraiment où est le problème (cf le code plus haut).
Marsh Posté le 27-03-2009 à 14:32:10
tomap a écrit : EDIT: |
Normal. Quand le thread principal dans main() recoit une connexion, il va l'ajouter dans la liste. Cool, sauf que le thread d'écoute, s'il est bloqué dans l'appel select() (donc plus d'une connexion), la liste des descripteurs sur lesquels la fonction écoute n'incluera evidemment pas la nouvelle connexion. Connexion qui sera incluse seulement lors de la réception du message suivant.
Bref:
Marsh Posté le 27-03-2009 à 14:58:03
En fait avec cette méthode il y a un problème de conception.
J'ai un thread d'écoute (main) qui attend et accept les connexions (sockets clients) et les ajoute dans une liste. Ok.
Et j'ai le thread (task_client) qui ajoute les sockets de la liste dans FD_SET puis se bloque sur select(), à chaque tour de boucle. Ok, mais y a un problème:
quand un client_1 se connecte, il est ajouté à la liste, puis il est ajouté à FD_SET() et le thread task_client se bloque sur select() en attendant de recevoir des données des clients qui sont ajoutés avec FD_SET (pour l'instant y a que client_1), quand un autre client_2 se connecte il est ajouté dans la liste mais il n'est pas encore ajouté à FD_SET() puisque le thread task_client est bloqué sur select() en attendant des données de client_1. Donc client_2 sera ajouté à FD_SET() au prochain tour de boucle, c'est a dire seulement quand select() se débloque en recevant des données de client_1, d'où le problème bizarre que j'ai cité dans le poste si-dessus.
Je ne vois donc toujours pas comment faire. Est ce que select() peut nous informer qu'il y a une connexion d'un client qui arrive, pour pouvoir utilisé accept() quand c'est le cas ?
Marsh Posté le 27-03-2009 à 15:06:06
tpierron a écrit :
|
Sorry, j'ai posté sans voir ton message.
Peux-tu mieux expliquer comment "notifier ton thread d'écoute en envoyant un message de service sur une des sockets", je n'ai pas compris.
Merci
Marsh Posté le 27-03-2009 à 15:20:13
tomap a écrit : Peux-tu mieux expliquer comment "notifier ton thread d'écoute en envoyant un message de service sur une des sockets", je n'ai pas compris. |
Bah, dans ta boucle, tu regardes déjà quelle commande tu recoies. Alors rajoutes une commande bidon pour notifier le thread en écoute que la liste des sockets à changé :
Code :
|
Et dans le main() :
Code :
|
Pas trop beaucoup testé, mais tu vois l'idée.
Marsh Posté le 27-03-2009 à 17:28:10
tpierron a écrit :
|
Ben 'notify' sera initialisé avec le 1er socket de la liste, et quand on fait send(notify, "OOB", 3, 0); ça va envoyer "OBB" au client qui s'est connecté avec le socket 'notify' (le 1er client de la liste). ça ne va donc pas envoyer un message au thread pour débloquer select().
Marsh Posté le 27-03-2009 à 18:43:20
tomap a écrit : Ben 'notify' sera initialisé avec le 1er socket de la liste, et quand on fait send(notify, "OOB", 3, 0); ça va envoyer "OBB" au client qui s'est connecté avec le socket 'notify' (le 1er client de la liste). ça ne va donc pas envoyer un message au thread pour débloquer select(). |
Ah ouais, c'est n'importe quoi ce que je t'es raconté. En fait pas tout à fait, et même si c'est limite de la bidouille, le premier client à se connecter sur le serveur, devrait être justement ton thread d'écoute, comme ça tu pourras interrompre l'attente sur le select en passant des messages directement au thread.
L'autre solution, c'est d'utiliser un thread par connexion (Apache fonctionne comme ça par exemple).
Marsh Posté le 27-03-2009 à 19:15:54
tpierron a écrit : le premier client à se connecter sur le serveur, devrait être justement ton thread d'écoute |
Je n'ai pas bien compris, tu veux dire qu'à chaque connexion d'un client, j'envoie une requête au 1er client pour qu'il me renvoi une repense pour que ça débloque l'attente sur le select() ? C'est trop à l'arrache ça..
J'ai eu l'idée suivante: à chaque connexion d'un client j'envoie un message à moi même (127.0.0.1) via un scoket udp qui est permanent dans la tête de la liste (avec sendto()). Donc j'ai fait:
Code :
|
Malheureusement, ça n'as pas l'aire de marché. select() ne se débloque pas lorsqu'il y a des données à recevoir venant d'un socket udp (envoyés avec sendto) ?
tpierron a écrit : L'autre solution, c'est d'utiliser un thread par connexion (Apache fonctionne comme ça par exemple). |
C'est le plus simple, malheureusement l'application serveur que je veux faire dois permettre la connexion de plusieurs clients (un chat instantané) et donc si je fait comme ça il y aura beaucoup trop de threads lancés au niveau du serveur, d'où mon utilisation actuel de select.
Marsh Posté le 27-03-2009 à 20:23:40
tomap a écrit : |
Ca dépend ce que tu appelles "trop de threads". Si c'est 100000, c'est non (avec les architectures standards), mais si c'est 1000 ou moins, ton système le supportera. Mais pour un chat, t'es forcément en dessous, car au dela de 50-100 personnes, ça devient vite le bordel, et 50-100 threads, c'est rien du tout.
Marsh Posté le 27-03-2009 à 20:58:12
Je n'aimerai pas être limité par le nombre de threads et du coup limité le nombres de clients, on ne sais jamais, je veux prévoir ça dés le début.
Sinon y a pas un autre moyen pour débloquer select() à partir du thread principale ?
Marsh Posté le 27-03-2009 à 21:22:39
Rah, mais pourquoi te casses tu la tête avec une socket UDP ?
Dans ta fonction task_client, la première chose que tu fais, c'est de te connecter (TCP) à ton serveur qui est bloqué présentement sur accept(). Du genre :
Code :
|
Normallement avec ça, ton thread principal devrait pouvoir débloquer ton thread d'écoute.
Edit: pour rejoindre ce que dit xilebo, fait aussi gaffe avec les FD_SET. Tu ne pourras pas multiplexer des milliers de connexions avec cette méthode de toute façon. Pour info sur Win32, FD_SETSIZE est définit à 64. 64 threads, ça ne représente rien comme charge.
Marsh Posté le 27-03-2009 à 22:23:06
Bon ok je vais faire comme ça alors.
tpierron a écrit : Edit: pour rejoindre ce que dit xilebo, fait aussi gaffe avec les FD_SET. Tu ne pourras pas multiplexer des milliers de connexions avec cette méthode de toute façon. Pour info sur Win32, FD_SETSIZE est définit à 64. 64 threads, ça ne représente rien comme charge. |
Oui mais je peux créer par exemple 5 threads comme task_client qui utilisent select() et j'aurai donc géré 64*5 client en ne créant que 5 threads, au lieux de 64*5 threads si je crée un thread par client connecté.
Mais bon je pourrais aussi faire me simplifié la vie en créant un thread par client connecté et définir une limite maximal de clients connecté et mettre en place 2 ou 3 serveurs; si le 1er est à la limite du nombre de clients autorisé, on envoi une requête au client qui veux se connecter pour le redirigé au 2eme serveur et ainsi de suite... Je vais y réfléchir.
Marsh Posté le 23-03-2009 à 16:46:53
Salut, est ce que quelqu'un sais où je pourrais trouver de la doc concernant l'utilisation des sockets asynchrones en langage C ? C'est beaucoup différents par rapport au sockets synchrones classiques ?
Merci.