Serveur Multi-Clients [Résolu]

Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
- - Dernière réponse : cs_Jack
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
- 20 mai 2011 à 00:04
Bonjour, bonsoir à tous

Je viens sur ce forum car je suis entrain de programmer un logiciel de conversation et je rencontre un petit problème.
Mon client utilise la classe CSocketMaster et le serveur utilise le contrôle Winsock.
Tout se passe bien pour la connexion, le serveur reconnait bien tout les clients. Le problème vient quand un client se déconnecte. J'ai mis des instructions dans l'évènement Close du Winsock serveur. Quand un client se ferme, parfois, le serveur détecte que le client a fermé et exécute alors le code qu'il y a dans l'évènement Close et parfois, il ne détecte rien et n'exécute même pas l'évènement Close. Savez-vous à quoi cela est dû ?

Voici une partie de mon code du serveur (car je crois que le bug vient de là ?):

sckServ(index) est le socket chargé lors d'une demande de connexion sur un socket principal.
La variable Nb est une variable de type Long et contient le numéro des sockets.

Private Sub sckServ_Close(Index As Integer)
 sckServ(Index).Close 'Ferme la connexion
 AddLog "Serveur " & Index & " Fermé" 'Ajoute au log que le client est fermé
 Unload sckServ(Index) 'Décharge le socket correspondant
 For i = 0 To lstIPClients.ListCount - 1
    lstIPClients.Selected(i) = True
    sckServ(Split(lstIPClients.Text, ";")(0)).SendData "YRC" & UName 'Dis aux clients qu'ils doivent retirer de la liste des connectés
    DoEvents
 Next i
End Sub


Si vous avez besoin d'autres infos ou si je n'ai pas été assez précis, vous pouvez me le dire.

Merci d'avance de votre aide.

Bonne prog à tous.

(PS: Je ne suis pas expert en programmation et surtout moins en gestion de socket en VB6)
Afficher la suite 

Votre réponse

15 réponses

Meilleure réponse
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
3
Merci
Salut

On est bien d'accord : le code que tu nous montres appartient au logiciel serveur. Les clients étant les programmes qui se connectent à ce serveur.

Peut-être que le _Close ne se déclenche pas parce que le client ne se déconnecte pas proprement, c'est à dire :
- n'utilise pas la commande .Close : à vérifier, notamment lors d'une fermeture brutale du programme
- utilise la commande .Close mais que son propre programme se ferme immédiatement, sans laisser le temps au composant d'envoyer sa demande. --> Toujours mettre (au moins) un DoEvents après ce genre de commande qui nécessite d'être traitée par un composant matériel, lié au système.

J'ai pas bien compris à quoi sert ta boucle For-Next qui suit.
Ton commentaire le laisse présumer, mais je ne voit pas trop d'où sort le UName; m'enfin c'est un autre problème.

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)

Dire « Merci » 3

Quelques mots de remerciements seront grandement appréciés. Ajouter un commentaire

Codes Sources a aidé 105 internautes ce mois-ci

Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Bonjour,

Merci beaucoup Jack, j'avais en effet oublié un DoEvents après la commande Close.
J'ai refais des tests et ça a l'air de fonctionner maintenant.

Et pour le UName et la boucle, en fait, ça contient le nom de l'utilisateur qui s'est déconnecté et ça envoie une commande à tous les clients encore connectés (donc présents sur ma Listbox) suivi du nom de l'utilisateur qui vient de se déconnecter afin que les clients suppriment dans leur Listbox l'élément correspondant au nom de l'utilisateur qui vient de partir pour que l'utilisateur sache quel autre utilisateur est encore connecté.

Encore merci, et bonne prog :)
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
Nulle part je ne vois le renseignement de la variable UName.
Elle contient surement quelque chose, mais pas forcément le nom de celui qui fait Close.
D'autre part, si tu envoies "YRCToto" en supposant que l'utilisateur à supprimer soit Toto, comment, à la réception, tu sais que la chaine transmise est terminée ?
Il faut se mettre en tête que les données peuvent arriver en rafale, en cas de surcharge du PC qui n'a pas le temps d'envoyer une première trame et qui enverra les deux en, même temps.
Dans ce cas, tes clients peuvent recevoir "YRCTotoYRCLulu"
Et comme tu ne sais pas où se trouve la fin d'une trame, les clients chercheront un client à supprimer du nom de TotoYRCLulu, qui posera problème.
Il faut toujours :
- Soit annoncer le nombre de bytes-caractères à suivre, exemple YRC#4#Toto
- Soit ajouter un caractère de bornage signalant la fin d'une trame, par exemple un § ou un Chr(0), n'importe quel caractère qui ne ressemble pas à un caractère usuel qu'on pourrait trouver dans les données,
afin que côté client, on puisse identifier sans erreur possible la fin d'une trame.
Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Bonjour Jack,

Comme je l'avais dit, je ne suis pas un expert en programmation et je ne savais pas du tout ce que tu m'a expliqué. Je vais revoir toutes mes données envoyées avec Winsock car je viens de me rendre compte que ma manière d'envoyer des données n'est pas sûre du tout. Je vais ajouter à chaque fois un caractère en fin de donnée pour confirmer que la trame est terminée, de cette façon, si 2 trames sont envoyées en même temps, je peux les séparer et en cherchant un peu, je pourrai les traiter une après l'autre.
Merci beaucoup d'avoir pris la peine de m'expliquer ça.
J'ai une autre question qui me vient à l'instant: Si plusieurs client envoient une donnée en même temps. Comme j'utilise des Winsock indexés et une seule Sub _DataArrival. Est-ce que la Sub peut être exécutée plusieurs fois en même temps ? Et est-ce qu'il n'y aura pas de conflits (sachant que chaque donnée reçue par DataArrival se stocke dans une variable locale dans le DataArrival) ?

Merci.
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
De rien, c'est un sujet que j'aime beaucoup.
Le _DataArrival ne peut pas se déclencher en même temps, mais éventuellement, à la suite. Par contre, prends garde de ne pas mettre de DoEvents dans un _DataArrival : l'empilement d'appel d'une même Sub pourrait poser des problèmes.

En effet, si tu utilises la même variable de stockage entre les arrivées, tu vas avoir un problème. Le plus simple étant d'indexer la variable de stockage (même index que le socket)
Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Bonjour,

Quand tu dis "indexer la variable de stockage" je ne comprends pas trop ce que tu veux dire par là. Je suppose que c'est créer un tableau dans la variable ?
Du genre:

Dim data() As String 'Ma variable qui va contenir la donnée reçue
sckServ(Index).GetData data(index)


Si oui, j'avais déjà essayé au début et j'avais une erreur "35600 Index Out Of Bounds" (car j'ai la version anglaise de VB6. Je crois que cela corresponds à "Index hors limites" dans la version française)

Et pour le DoEvents dans le _DataArrival, je ne vois pas comment m'en passer. Car quand le serveur reçoit un message d'un client, il doit l'envoyer à tous les clients. Pour cela, j'ai fait une boucle comme ceci (extrait du _DataArrival):

data = Mid(data, 4)
txtConvers.Text = txtConvers.Text & data & vbCrLf 'txtConvers contient la conversation des clients
For i = 0 To lstIPClients.ListCount - 1 'lstIPClients contient des infos sur les clients connectés
lstIPClients.Selected(i) = True
sckServ(Split(lstIPClients.Text, ";")(0)).SendData "SMS" & Data & Chr(0)
DoEvents 'Sans le DoEvents ici, ça n'envoie pas le message à tout le monde
Next i
txtConvers.SelStart = Len(txtConvers.Text)


Merci.

Gabriel.
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
Oui, une variable indexée.
Dim data() As String 'Ma variable qui va contenir la donnée reçue
Très bien. Il suffira de redimensionner ce tableau à chaque fois que tu crées aussi un nouveau Winsock lors de la connexion d'un client.
Bien sûr, cette déclaration doit être placée dans la partie Déclarations de ta page de code pour que son contenu survive entre deux appels de _DataArrival.

Par contre, ne l'utilise pas comme tu as prévu de le faire :
sckServ(Index).GetData data(index)
Si tu lis les données et que tu les stockes directement dans ta variable indexée, tu vas écraser le précédent contenu de cette variable. C'est justement ce qu'on veut éviter en ayant gardé sous le coude des données qui n'ont pas pu encore être traitées car incomplètes.
Il faut donc lire les données su Winsock, puis les ajouter à la variable tableau
Dim sTemp As String
sckServ(Index).GetData sTemp, vbString, bytesTotal ' syntaxe complète, tant qu'à bien faire
data(index) = data(index) & sTemp
NB : Data était un mot clé du langage (QBasic) --> Essaye de trouver un nom plus perso pour éviter d'éventuels problèmes.

Pour le traitement des données, je te conseille de créer un simple Timer qui, toutes les secondes, viendra parcourir les données de cette variable indexée ET vérifiera que des données sont complètes.
- Si données complète (au sens de ton protocole, qui possède la balise de fin d'envoi ou dont la longueur est celle attendue), alors tu traites le contenu, sans oublier de conserver ce qui suit la première trame traitée : ta variable peut très bien contenir plusieurs trames complètes --> boucle
- Si les données ne sont pas complètes, rien à faire, tu attends le prochain cycle pour retester.
Commenter la réponse de cs_Jack
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
PS : Tu ne sembles pas familier des variables indexées.
Si tu crées un tableau
Dim Data() As String
il faudra, à un moment donné, lui fournir la dimension du tableau.
C'est le rôle de ReDim et ReDim Preserve.
Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Ah oui, d'accord, je viens de me renseigner sur ReDim et ReDim Preserve.
Dans mon cas, je crois que ReDim Preserve serait plus approprié d'après ce que j'ai compris.

Pour le stockage des données, si j'ai bien compris, la variable indexée va être une sorte de file d'attente pour l'exécution des commandes. Mais est-ce que le Timer avec son délai ne vas pas poser problème et ralentir tout si plusieurs personnes parlent en même temps dans la conversation directe ou si plusieurs données arrivent en même temps ?
Ou bien est-ce que le _DataArrival ne vas pas se tromper et mettre la donnée dans le mauvais Index de la variable ?

Et pour finir, une dernière question: Dans le Timer, comment je ferai pour lire la donnée arrivée la première (et la supprimer quand elle sera exécutée) si par exemple, il y a 2 ou 3 données complètes ?

Encore merci.

Gabriel
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
Absolument aucune crainte de mélange (du moment que tu respectes les index).
Si le _DataArrival a ajouté des données durant l'exécution du Timer, ces données seront à la fin.

Supposons que 3 messages soient arrivés, le 3ème étant incomplet.
Dans ta variable 'buffer' indexée (un buffer par Winsock), tu auras un truc ressemblant à ceci :
#Message 1§#Message 2§#Messag
en supposant que les bornes de début sont des # et les bornes de fin soient des § et que "Message 1" renferme la structure de trame que tu as imaginée
Lors du traitement, tu vas :
-1- vérifier qu'il existe un § dans le buffer --> Instr.
Oui, on en voit un après le '1' : cela veut dire qu'il y a un message complet à traiter
-2- Donc, tu vas isoler depuis le début de la chaine jusqu'à ce § (--> Left$), puis tu vas découper les éléments de cette trame la traiter pour en faire ce que bon te semble.
-3- Ensuite, il faudra supprimer du buffer la trame qui vient d'être traitée, c'est à dire de la gauche du buffer jusqu'au § -> Mid$ ou Right$
-4- Puis, si le buffer contient encore des caractères, il faut recommencer en -1-, jusqu'à ce que le contenu du buffer soit vidé OU qu'il ne reste qu'une trame incomplète (pas de § final)
Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Bonjour, (et désolé du retard)

Je viens d'essayer de reprogrammer le serveur pour avoir les données dans une variable indexée mais je n'arrive pas à redimensionner le tableau qui contient les données reçues et a chaque fois, j'ai une erreur "35600 - Index out of Bounds"

Et maintenant, pour couronner le tout, le serveur ne transmet plus les messages à tous les clients.
Je me demande si ma méthode pour envoyer des messages est correcte.

La Listbox lstClients dans ce code contient les index des clients connectés

For i = 0 To lstClients.ListCount -1 'Pour ne pas envoyer à des index non connectés
    lstClients.Selected(i) = True
    sckServ(lstClients.Text).SendData "donnée"
    DoEvents
Next i


Voilà comment je procède pour envoyer des messages à tous les clients mais ça n'envoie qu'a certains clients.

Pouvez-vous m'éclairer encore un peu s'il vous plaît ?

Merci beaucoup.


Gabriel
Commenter la réponse de djgab21
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Rectification du message précédent: J'ai renommé ma variable Data comme conseillé plus haut et je n'ai plus l'erreur "35600 - Index out of Bounds" pour l'instant.

Cependant, le problème d'envoi de message aux client est resté.
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
"je n'arrive pas à redimensionner le tableau"
Comment l'as-tu dimensionné ?
Qu'as-tu écrit pour le redimensionner ?

Une erreur, Ok, mais sur quelle ligne ?

Dans ton code, que vaut "lstClients.Text" au fur et à mesure de ta boucle / quand survient l'erreur ?.
Il faut que ce soit un index valide.

Dans ton exemple, tu envoies le texte "donnée"
Normal ?

Pour récupérer le texte d'une ListBox, pas besoin de sélectionner chaque item :
For i = 0 To lstClients.ListCount -1
    sckServ(lstClients.List(i)).SendData "donnée"
    DoEvents
Next i
Commenter la réponse de cs_Jack
Messages postés
66
Date d'inscription
vendredi 15 juin 2007
Dernière intervention
19 mai 2011
0
Merci
Bonjour,

Concernant le redimensionnement du tableau, j'ai réussi à le faire finalement. Je n'utilisais pas bien la fonction Redim Preserve et le nom de la variable "Data" faisait conflit.

Maintenant, concernant l'envoi des données aux clients, la Listbox "lstClients", elle contient les index des clients connectés et authentifiés.
Au fur et à mesure de la boucle, je sélectionne l'Item suivant grâce à ce code :
lstClients.Selected(i) = True
puis, je lis le texte sélectionné de la Listbox (qui contient donc l'index d'un client connecté).

Je vais maintenant tester avec votre code ( lstClients.List(i) ) pour sélectionner l'index auquel envoyer la donnée. Sinon, je pensais aussi faire avec une variable. Ca ne serait pas mieux avec une variable qui contient les index des sockets connectés ?


Gabriel.
Commenter la réponse de djgab21
Messages postés
14010
Date d'inscription
samedi 29 décembre 2001
Dernière intervention
28 août 2015
0
Merci
Plus rapide, surement, mais pas grand chose puisque le nombre de clients n'est pas grand.

Revois ta structure de programme.
Pour chaque connexion de client, tu as plusieurs informations à conserver.
Au lieu de créer une foultitude de tableaux indexés, mieux vaudrait indexer une seule variable mais de type Structure, exemple :
Déclaration :
Public Type typeMesParametres
    sPseudo            As String
    WinsockIndex       As Integer
    dteHeureConnexion  As Date     ' exemple
End Type
Dimensionnement :
Public MesParametres() As typeMesParametres
Utilisation :
Comme une variable simple, avec Redim Preserve
En supposant que tu veuilles lire les infos de la 2ème donnée (tableaux commencent à 0)
MesParametres(1).sPeudo = "Toto"
MesParametres(1).WinsockIndex = 3

Consulte l'aide de Type
Commenter la réponse de cs_Jack

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.