Thread et réseau

Vous débutez et vous avez besoin d'aide ? N'hésitez pas à poser vos questions
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Thread et réseau

Message par blendman »

salut

J'ai commencé à regarder un peu comment ajouter une fonction style "réseau" LAN (ou via internet) pour mon jeu Arkeos Chronicle.
J'ai réussi, grâce à l'exemple client\server à créer un système de server + client, ajouter des clients, transférer des messages différents, faire des actions différents (client se connecte/client se déconnecte, client bouge, tchat...) et tout fonctionne très bien.

Ensuite, j'ai voulu ajouter des mobs depuis le serveur vers les clients. Du coup, pour que tout soit encore fluide, j'ai ajouté un thread pour les mobs.
Et là, je rencontre un petit problème.

Dans mon serveur, j'utilise des map() pour gérer mes joueurs, mobs, fx, etc..
Je mets un identifiant en tant que clef$ pour les joueurs. Cet identifiant, je l'obtiens lorsque je fais ceci dans le client :

Code : Tout sélectionner

OpenNetworkConnection(server$, Port)
Par exemple, cela renvoie 114586585 et hop, ça me fait mon identifiant du coté serveur, pour ce joueur.


Le problème est que j'utilise donc un thread pour les mobs. Lorsqu'un mob bouge, je dois envoyer aux clients qu'il a bougé (et les coordonnées).

Je fais donc ceci (pour le moment j'envoie en SendNetworkString, mais je passerai ensuite en data je crois que c'est mieux).
C'est juste pour tester le déplacement, hein ;).

Code : Tout sélectionner

For i=0 To MapSize(mob())-1
    If mob(Str(i))\cibleOK <= 0 
      mob(Str(i))\cibleOK = 500
      mob(Str(i))\cibleX = mob(Str(i))\x + Random(50)-Random(50)
      mob(Str(i))\cibley = mob(Str(i))\y + Random(50)-Random(50)
    Else 
      mob(Str(i))\cibleOK -1  
      If mob(Str(i))\x >mob(Str(i))\cibleX
        mob(Str(i))\x-1
      ElseIf  mob(Str(i))\x <mob(Str(i))\cibleX
        mob(Str(i))\x+1      
      EndIf 
      If mob(Str(i))\y >mob()\cibley
        mob(Str(i))\y+1
      ElseIf  mob(Str(i))\y <mob(Str(i))\cibley
        mob(Str(i))\y-1
      EndIf
      If Mob(Str(i))\x = mob(Str(i))\cibleX And mob(Str(i))\y =mob(Str(i))\cibley
        mob()\cibleOK = -1
      EndIf
    EndIf
    If mob(Str(i))\cibleOK > 0 And Not(Mob(Str(i))\x = mob(Str(i))\cibleX And  mob(Str(i))\y =mob(Str(i))\cibley)
      If MapSize(ListClient())>0
        ForEach ListClient()
          FindMapElement(ListClient(), Str(ListClient()\id))
          SendNetworkString(ListClient()\id , Str(#SM_MobMove)+";"+Str(mob(Str(i))\MapId)+";"+Str(mob(Str(i))\x)+";"+Str(mob(Str(i))\y)+";")
          ;Delay(15) ; utile ?
        Next 
      EndIf      
    EndIf      
  Next i
Et là, le server plante en me disant :
la map listclient() n'a pas d'élément courant.

Bon, je suppose que la raison est que cette map est partagée entre ce thread et le reste du programme (j'ai d'autres procédures qui utilisent :

Code : Tout sélectionner

 ForEach ListClient()
 SendNetworkString(ListClient()\id ,...)
next 
Mais savez-vous comment on fait ce genre de chose dans un server\client : envoyer l'état des mobs par exemple aux clients dans un thread ?
J'imagine qu'il faut faire une "pile" et qu'on envoie les données toutes les 10ms par exemple, mais comment ?
Il faut peut être utiliser les mutex ou semaphore ?
Mais je ne comprends pas encore comment utiliser tout cela pour un server\client.

Si vous avez des informations ou des conseils supplémentaires sur l'organisation d'un code server\client, n'hésitez pas ;).
stombretrooper
Messages : 117
Inscription : dim. 21/déc./2008 18:39

Re: Thread et réseau

Message par stombretrooper »

La solution c'est sans doute les mutex. Avec les détails que tu donnes, on dirait que ta map est utilisé simultanément sur deux thread différent. Pour éviter ce genre de problème, les mutex sont là !

Un mutex c'est un élément qui va permettre de bloquer un thread tant qu'un autre thread n'a pas libéré le mutex.

Code : Tout sélectionner

mutex_id= CreateMutex()
LockMutex(mutex_id) ; <= block le mutex, si le mutex est déjà bloqué, le thread s'arrête et reste sur le LockMutex jusqu'à ce que l'autre thread libère.
... tes actions ...
UnlockMutex(mutex_id) ; <= libère le mutex, d'autre thread peuvent le bloquer.
Le seul truc que tu as à faire c'est partager la variable mutex_id, pour ça, je te conseils de faire une structure, la mettre en 'global', et mettre tes mutex dedans. Essaye de faire un mutex pour chaque liste, tableau, map, variables partagées.

Tu prépares un mode co-op sur Arkeos ?


edit : à ce propos, j'ai une question sur ce genre de chose (j'ai pas cherché, mais bon vue que blendman pose une question sur les thread et le réseau, j'en profite) :

La lib network n'est pas threadsafe non ? J'ai des problèmes de connections de client plutôt gênant, il y a une méthode pour utiliser directement les sockets et/ou avoir la lib en threadsafe ? Mon principal problème, c'est quand un client se déconnecte, et qu'un thread était en train de dialoguer avec ce dernier, il arrive souvent le plantage 'le client spécifié n'existe pas' (une erreur du genre quand on envoie des données sur un client inexistant), c'est surtout qu'il n'existe plus en faite... Ça se joue à quelques dixièmes de secondes.

On m'a conseillé pour ce genre de grosse appli serveur, de créer un thread par 'socket' (et donc par client), mais avec la lib network, justement on travaille pas avec des sockets, c'est quasiment impossible de séparer un lien serveur-client sur un thread, à part en connectant un seul client par port, et en faisant une centaine de serveurs, mais je penses pas que ce soit une méthode très propre.
http://www.purebasicstreet.com/ - Site dédié à purebasic.
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: Thread et réseau

Message par G-Rom »

La chaîne de caractère est à proscrire pour le traitement de donnée à travers le réseau pour un jeu.
tu dois inventé ton protocole de communication.

je m'explique , tu dois allégé un max tes messages , pas de superflu , le fait d'envoyer les coordonnées du serveur aux autres clients fera une animation saccadé du joueur. les paquets qui traversent le réseau n'arrivent pas à intervalle régulier même si pour toi c'est instantané, c'est pas le cas en réalité.
Tes messages doivent être court , identifiable par celui qui le lit avec le premier byte :

par exemple un code de correction de coordonnée :

Code : Tout sélectionner

; LA POSITION A ENVOYER A TRAVERS LE RESEAU
PosX.f = 27.768
PosY.f = 32.865
PosZ.f = 48.489

#MESSAGE_CORRECTION_COORDONNE = $FF ; ENTETE DU MESSAGE
*Message = AllocateMemory( 1 + (3 * 4) )  ; ALLOCATION DE LA MEMOIRE NECESSAIRE
                                          ; 1 BYTE POUR L'ENTETE
                                          ; 4 BYTE POUR UN FLOAT
                                          ; 4 BYTE POUR UN FLOAT
                                          ; 4 BYTE POUR UN FLOAT
                             
; On poke....
PokeA( *Message , #MESSAGE_CORRECTION_COORDONNE)
PokeF( *Message+1     , PosX)
PokeF( *Message+1+4   , PosY)
PokeF( *Message+1+4+4 , PosZ)

; On peek....
Debug PeekA(*Message)
Debug PeekF(*Message+1)
Debug PeekF(*Message+1+4)
Debug PeekF(*Message+1+4+4)
le client recevra un message de taille inconnu , il n'aura qu'a lire le premier byte pour connaître la taille du message puisque c'est toi qui le défini en créant ton protocole.

Pour un truc fluide et costaud , opte déjà pour le serveur dédié, pas d'affichage pour lui , juste une console d'admin.
n’envoie pas en temps réel les données , envois avec une latence de 25/30ms max tout les paquets préalablement fabriqué.
n'envoie pas des coordonnée brut pour effectué un mouvement sur les clients, mais des actions :

- Client XXX à appuyer sur avancer
- Serveur reçois l'info du de l’appui du Client
- Le Serveur avance le joueur
- Le Serveur renvois l'info à tout les clients , y compris celui qui a appuyé la touche.
- Les Clients reçoivent l'info du serveur et met à jour l'affichage.

Faut bien que tu comprennent que ce n'est pas le client qui décide de sa position , mais le serveur.
tu n'as plus qu'a faire une correction de coordonnée ( à cause de la latence ) tout les 100ms par exemple. (serveur->clients) et pas l'inverse la correction , sinon faille pour la triche :D
si je n'ai pas été clair , dis le moi.
Backup
Messages : 14526
Inscription : lun. 26/avr./2004 0:40

Re: Thread et réseau

Message par Backup »

G-Rom a écrit : tu n'as plus qu'a faire une correction de coordonnée ( à cause de la latence ) tout les 100ms par exemple. (serveur->clients) et pas l'inverse la correction , sinon faille pour la triche :D
si je n'ai pas été clair , dis le moi.
peut etre aussi définir qu'un Avancement , fait toujours la meme taille , de cette façon, pas besoin que cette correction soit fréquente

si le client reçoit 2 avances, et que chaque avance est de 5 pas (grille définie pour tout le monde) , le client réactualise l'avance +10 pas

il se pourra bien sur que le serveur soit pas tout a fait synchro avec le client (le client aurai pas reçut la derniere avance)
mais c'est ce qu'on appel le LAG :) (présent dans tout les jeux en reseau...)
lorsque la mise ajour se produit, ça provoque une saccade ..
Dernière modification par Backup le jeu. 02/févr./2012 14:14, modifié 1 fois.
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Re: Thread et réseau

Message par blendman »

G-Rom a écrit :le client recevra un message de taille inconnu , il n'aura qu'a lire le premier byte pour connaître la taille du message puisque c'est toi qui le défini en créant ton protocole.
Merci pour ton exemple, je pense que j'ai à peu près compris ;).

J'utilise déjà le premier byte du message comme entête de type de message :

Code : Tout sélectionner

#MESSAGE_CONNEXION = $0
#MESSAGE_LOGIN_MDP = $1
#MESSAGE_CHOIX_PERSONNAGE = $2
#MESSAGE_COORDONNEES = $50
#MESSAGE_DECONNEXION = $FE
; etc...

J'imagine qu'en fonction 1er byte (l 'entête de message), l'allocation memoire ressemble à ça :

Code : Tout sélectionner

*Message = AllocateMemory( 1 + (3 * 4) ); pour message coordonnées
*Message = AllocateMemory( 1 +2+1+2+2 ); pour message type new perso : id.w/peuple.a/x./y.w
; etc..
Mais pour un pseudo ou un message de tchat par exemple, je dois faire ceci (car c'est du texte, donc je décide moi-même de sa taille)?

Code : Tout sélectionner

; tchat :
*Message = AllocateMemory(100); on suppose qu'on interdit plus de 100 caractères pour du tchat
; pseudo :
*Message = AllocateMemory(25); pas plus de 25 caractères par pseudo. 

; On poke....
PokeA( *Message , #MESSAGE_CORRECTION_COORDONNE)
PokeF( *Message+1 , PosX)
PokeF( *Message+1+4 , PosY)
PokeF( *Message+1+4+4 , PosZ)
ce n'est pas ça plutôt ? :
; On poke....
PokeA( *Message , #MESSAGE_CORRECTION_COORDONNE)
PokeF( *Message+4 , PosX)
PokeF( *Message+4+4 , PosY)
PokeF( *Message+4+4+4 , PosZ)
En tout cas, merci beaucoup pour ton explication ;).

il faut utiliser ça pour l'envoyer, j'imagine ?

Code : Tout sélectionner

SendNetworkData(ClientID,*Message,1+4*3) ; pour le message coordonnées 
Pour un truc fluide et costaud , opte déjà pour le serveur dédié, pas d'affichage pour lui , juste une console d'admin.
Server dédié, tu veux dire un server en dehors du client ?
Si oui, c'est déjà le cas (et en console) :).
n’envoie pas en temps réel les données , envois avec une latence de 25/30ms max tout les paquets préalablement fabriqué.
là, ça m'intéresserait d'avoir un peu de précision, car c'est là-dessus que je bloque un peu :
- Créer la "pile" de paquet
- on envoie tous les paquets à tous les clients en même temps (enfin, à la suite) ? ou on met un léger délai ?

Voilà un exemple de ce que je pensais faire, est-ce la méthode pour gérer un thread d'envoi de paquet et la réception de ces paquets (message des clients)?

Code : Tout sélectionner

;{ les entêtes de messages
#MESSAGE_CONNEXION = $0 
#MESSAGE_LOGIN_MDP = $1
#MESSAGE_CHOIX_PERSONNAGE = $2
#MESSAGE_COORDONNEES = $50
#MESSAGE_TCHAT = $64 ; 100
#MESSAGE_DECONNEXION = $FE
;}

;{ variables, globales
Global timer.w,ClientID.l,*buffer 
;}

;{ structures
Structure StPaquet
  txt$
  size.w ; la taille memoire du paquet
  *paquet
EndStructure
Global NewList paquet.StPaquet()

Structure stClient
  id.i
  nom$ : pass$
  status.a; en attente, connecté...
  NumMap.w
  x.w : y.w
  peuple.a
EndStructure
Global NewMap Client.StClient()

;}

;{ init
If InitNetwork()=0 : End : EndIf
*buffer =AllocateMemory(300); pour le tchat au cas où
;}

;{ procedure
Procedure ReceiveMessageFromClient(*var)
  packet.s = PeekS(*buffer,MemoryStringLength(*buffer))
  AddElement(paquet())  
  Select entete
      Case #MESSAGE_CONNEXION
        paquet()\Paquet= AllocateMemory( 1 + 2); on lui renverra son id.w
        ; comment enlever le #MESSAGE_CONNEXION ou décaler pour ne lire que le message ?
        PokeW(paquet()\Paquet,#MESSAGE_CONNEXION)
        
      Case #MESSAGE_LOGIN_MDP
        paquet()\Paquet = AllocateMemory( 1 + Len(Login$)+Len(Mdp$))
        ;paquet()\txt$ = PeekS(*buffer)
        
      Case #MESSAGE_CHOIX_PERSONNAGE
        paquet()\Paquet = AllocateMemory( 1 + 1); le choix du personnage est un .a
        PokeA(paquet()\Paquet,#MESSAGE_CHOIX_PERSONNAGE)
        ; etc...
    EndSelect
  FreeMemory(*buffer)
  *buffer =AllocateMemory(300); 300 c'est pour la longueur max (le tchat)
EndProcedure

Procedure SendPaquet(*var); thread pour gérer les paquets à envoyer
  Shared mutex
  LockMutex(mutex)
  If timer >0
    timer - 1 
  Else
    timer = 250 ; 25ms
    ForEach paquet() 
      ForEach client()
        SendNetworkData(ClientID,@paquet()\paquet,paquet()\size) 
      Next
      DeleteElement(paquet(),1)
      ;Break 
    Next
  EndIf
  UnlockMutex(mutex)
EndProcedure

;}

If OpenConsole() = 0 :  End :EndIf ; on ouvre la console
If CreateNetworkServer(0,6832)=0 : End :EndIf ;on crée la connexion server
PrintN("Server Connecte sur le port 6832")
mutex = CreateMutex()
Repeat
 KeyPressed$ = Inkey() 
 CreateThread(@SendPaquet() , 0)
 
 SEvent = NetworkServerEvent()
 If SEvent
   ClientID = EventClient() ; on reçoit l'id du client
   Select SEvent
     Case #PB_NetworkEvent_Connect ; connexion d'un nouveau client
       
     Case #PB_NetworkEvent_Data; réception et/ou envoi de donnée (client->server->clients)
       ReceiveNetworkData(ClientID,*Buffer,300)
       ReceiveMessageFromClient(ClientID)
       
     Case #PB_NetworkEvent_Disconnect ; deconnexion d'un client

   EndSelect
 EndIf
 CreateThread(@SendPaquet(),0)
 
Until KeyPressed$ = Chr(27); Echap



n'envoie pas des coordonnée brut pour effectué un mouvement sur les clients, mais des actions :
- Client XXX à appuyer sur avancer
- Serveur reçois l'info du de l’appui du Client
- Le Serveur avance le joueur
- Le Serveur renvois l'info à tout les clients , y compris celui qui a appuyé la touche.
- Les Clients reçoivent l'info du serveur et met à jour l'affichage.
ok, je comprends tout à fait le principe, c'est d'ailleurs comme cela que je pensais faire, mais il faut que je mette ça en pratique :).
Faut bien que tu comprennent que ce n'est pas le client qui décide de sa position , mais le serveur.
tu n'as plus qu'a faire une correction de coordonnée ( à cause de la latence ) tout les 100ms par exemple. (serveur->clients) et pas l'inverse la correction , sinon faille pour la triche :D. si je n'ai pas été clair , dis le moi.
tu as été très clair, j'ai tout compris je t'en remercie ;).
Après, il faut juste que j'arrive à coder ce que j'ai compris, et là, des fois, ça se corse :D.

Concernant les mouvements, je pensais faire ça :
- le joueur appuie sur une touche (right). On envoie soit l'appuie de la touche
- le server reçoit l'info
- 2 possibilités : soit le server renvoie l'info à tous les clients, soit le server crée une "cible" : la case d'à coté en fonction de la ou des touche(s) appuyée(s) : case à droite si on a appuyé sur key_right, etc.., puis il renvoie l'info (la cible) à tout les clients , y compris celui qui a appuyé la touche.
- Les Clients reçoivent l'info du serveur et mettent à jour l'affichage : autrement dit ils bougent le joueur vers cette case (qui est à coté) ou alors ils bougent simplement le client en fonction du message envoyé par le server.

J'ai testé avec des mobs et ça marche super bien, c'est excellent :). Les mobs ont un avantage par rapport aux clients, leur mouvements est définis par le serveur.

stombretrooper a écrit :La solution c'est sans doute les mutex. Avec les détails que tu donnes, on dirait que ta map est utilisé simultanément sur deux thread différent. Pour éviter ce genre de problème, les mutex sont là !
yep, j'ai commencé à regarder un peu les mutex. Je pense aussi que la map est utilisée sur 2 thread : le thread des mobs et le thread des clients (thread principal).
Du coup, j'ai ajouté un mutex dans le thred de déplacement des mobs et ça marche nickel, plus de lag ni de plantage, c'est magique :).

Un mutex c'est un élément qui va permettre de bloquer un thread tant qu'un autre thread n'a pas libéré le mutex.
Le seul truc que tu as à faire c'est partager la variable mutex_id, pour ça, je te conseils de faire une structure, la mettre en 'global', et mettre tes mutex dedans. Essaye de faire un mutex pour chaque liste, tableau, map, variables partagées.
j'ai crée un mutex en shared dans la procédure des mobs.
Mais je testerai ta méthode pour voir ;).

Tu prépares un mode co-op sur Arkeos ?
Disons que je fais des tests pour ça, on verra si j'y arrive :). En regardant Steam, j'ai vu que sur le formulaire de submission, on devait préciser si le jeu pouvait être en LAN (mode co-op) ou multi-joueurs.
Visiblement, les jeux sur lesquels il y a de la co-op ou des possibilités de LAN marchent vraiment bien, mieux que les autres semble-t-il.
Du coup, pour Arkeos, j'aimerai bien mettre un petit mode LAN , un truc très simple, genre :
- créer un server (local uniquement ou via internet ? je ne sais pas encore)
- server avec maximum 5 à 20 joueurs (enfin, je testera avec plus de joueurs pour être sûr que ça passe avec le minimum).
- pour les autres clients du réseau : rejoindre la partie/ quitter la partie, etc...

Ensuite, j'aimerai ajouter ça :
- création des mobs et gestion des mobs (apparition, déplacement, combat (perte de vie), mort, re-pop, depuis le client qui fait server) : ça c'est ok à priori.
- gestion des joueurs : apparition, déplacement, combat, mort, déconnexion, etc.. : à priori, ça semble fonctionner. Mais je dois revoir les threads car je n'ai pas encore fait de thread pour les clients ^^.
- gestion des fx : ça devrait être ok, car c'est comme les joueurs et les mobs.

Et c'est tout, à priori, pas de BDD (enfin, je vais quand même en ajouter une pour tester), car le reste serait normalement stocké en local, sauf si Steam est intéressé par le jeu, et demande une gestion plutôt type en ligne multi-joueurs que local (c'est à dire un server "dédié" gérant une BDD)
Mais là, ce serait tout autre chose, je ne sais pas si je le ferai du coup.
je pense qu'un petit mode co-op/LAN pourrait être un gros plus pour une version du jeu, avec une gestion d'une dizaine de joueurs par exemple.

La lib network n'est pas threadsafe non ? J'ai des problèmes de connections de client plutôt gênant, il y a une méthode pour utiliser directement les sockets et/ou avoir la lib en threadsafe ?
j'ai cherché pas mal d'info sur les thread, et j'ai pas vu de message permettant d'avoir du thread safe. Les mutex ne règlent pas le problème ?

Sinon, pour les sockets, j'ai trouvé ça, je pense que ça peut être intéressant (c'est un exemple client\server) :
http://www.purebasic.fr/english/viewtop ... ket+server
Mon principal problème, c'est quand un client se déconnecte, et qu'un thread était en train de dialoguer avec ce dernier, il arrive souvent le plantage 'le client spécifié n'existe pas' (une erreur du genre quand on envoie des données sur un client inexistant), c'est surtout qu'il n'existe plus en faite... Ça se joue à quelques dixièmes de secondes.
J'y avais déjà réfléchi, et je pense qu'une des solutions serait de faire comme le jeu MIxmaster ou d'autres jeux :
- un délai de déconnexion.

Dans Mixmaster, lorsque tu cliques sur déconnexion, le jeu mets 10 secondes pour te déconnecter. Tu ne peux plus rien faire pendant ce temps-là (tu peux bouger la souris et tu vois un compteur qui décompte la déconnexion).
Je pense que le client envoie à ce moment-là un message au server pour le prévenir qu'il souhaite se déconnecter et lui laisse le temps de "fermer" ou arrêter ce dont il se sert avec ce client.
En fait, tu pourrais même faire un truc de déconnexion du coté du server, comme lorsque tu éteins le server.

Voici la manière dont tu pourrais faire la déconnexion :
- le client envoie un message comme quoi le joueur a cliqué sur "déconnexion", mais il ne se déconnecte pas.
- le server reçoit ce message, il renvoie un message au client comme quoi c'est ok (deco_ok), il supprime le client de la liste des clients.
- le client reçoit le message du server : "deco_ok" et peut se fermer.

Si tu fais ça, ça devrait résoudre ton problème pour la plupart des cas j'imagine (sauf si on ferme le jeu avec ctr+alt+sup..).
On m'a conseillé pour ce genre de grosse appli serveur, de créer un thread par 'socket' (et donc par client), mais avec la lib network, justement on travaille pas avec des sockets, c'est quasiment impossible de séparer un lien serveur-client sur un thread, à part en connectant un seul client par port, et en faisant une centaine de serveurs, mais je penses pas que ce soit une méthode très propre.
Visiblement, on peut utiliser les sockets avec le pure, regarde le lien que j'ai mis plus haut ;).
Sinon, ici, une explication très bien détaillée pour l'utilisation :
http://www.purebasic.fr/french/viewtopi ... ver+client


Dobro : ton système me fait penser au système de case que je voulais mettre en place ;).

En tout cas, un très grand merci à vous pour toutes ces informations très précieuses, car grâce à vous, mon test server/client commence à être assez sympa :).
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: Thread et réseau

Message par G-Rom »

; On poke....
PokeA( *Message , #MESSAGE_CORRECTION_COORDONNE)
PokeF( *Message+4 , PosX)
PokeF( *Message+4+4 , PosY)
PokeF( *Message+4+4+4 , PosZ)
C'est faux , #MESSAGE_CORRECTION_COORDONNE à une taille de 1 , la prochaine écriture se fera à @Adresse + 1 ( 1 c'est l'offset )
tu te dois de maîtrisé les pointeurs à la perfection ;)
Fait toi une collection de fonction qui te permet de fabriqué des paquets facilement , ca t'évitera pas mal d'erreur par la suite :

du style :
CreatePacket.i(Header.a)
PushFloat(*Packet.sPacket, float.f)
PushInteger(*Packet.sPacket, integer.i)
...
PopFloat.f(*Packet.sPacket)
PopInteger.i(*Packet.sPacket)
...
Mais pour un pseudo ou un message de tchat par exemple, je dois faire ceci (car c'est du texte, donc je décide moi-même de sa taille)?
Ton message peu avoir une grosse taille , pas de pb , tu rajoute un flag de 4 byte après l'entete :

..........1 Byte............4 bytes.......1 byte par caractère
[ MESSAGE_STRING ] [ TAILLE 6 ] [ C ] [ O ] [ U ] [ C ] [ O ] [ U ]

C = 67 , O = 79 , etc...

Quand tu reçois [ MESSAGE_STRING ] , tu sais qu'après les 4 prochains bytes désigne la taille du message, ensuite tu as le message en lui même ( sans le #null de fin de string )
il faut utiliser ça pour l'envoyer, j'imagine ?
SendNetworkData ? oui , c'est mieux que SendMail() :D

là, ça m'intéresserait d'avoir un peu de précision, car c'est là-dessus que je bloque un peu :
- Créer la "pile" de paquet
- on envoie tous les paquets à tous les clients en même temps (enfin, à la suite) ? ou on met un léger délai ?
ton jeu fabrique des paquets en temps réel , pas dans la boucle bridé par l'affichage ( bridé par le FPS j’entends )
chaque paquet fabriqué part dans une liste chainée , tout les 25ms ( fait tes test de perfs pour paufiné le temps idéal avec des vrais clients distant ( je serais dispo ce week end si tu veut pour des tests ) tu balances les paquets partout , coté client & serveur donc , tout le monde vide sa pile. ( ca c'est les message important )

Tout les 100 ms , tu fait les corrections de syncro ( idem pour le temps , a faire des test aussi ) , la syncro concerne principalement les coordonnées , l’envoie de texte pour le tchat , l'envois de partie de fichier , etc... , le but est de ne jamais saturé la bande passante , crois moi ça va vite...
& tu écoutes en temps réel l'arrivé de nouveau paquets.
si ton programme est bien ficelé , tu n'auras pas besoin de thread, car si ils sont mal utilisé , ton réseau sera bancal.
je te conseil de te faire une collection de fct° dans des fichiers séparé ( sa pourrais te resservir dans d'autres projets d'ailleurs , faire une lib... ) bref, faire une couche d'abstraction supplémentaire à la lib network de pb.
si tu as compris tout ce que je t'ai dis , ton réseau devrais roulé comme du tonnerre de Zeus !
point clé , alléger un max les paquets , bien temporiser l’envoi tout les 20/25/30ms pour les message important , 75/100/150ms pour le reste.

ha oui , chaque client devras envoyé au serveur tout les 500ms un "I_AM_ALIVE" (je suis en vie)
si le serveur ne reçois pas ce message tout les 500 max , le serveur considéra comme déconnecté le client.
c'est une sorte de ping , ça permet surtout d'alléger la charge du serveur si un ou des joueurs n'ont pas quitté correctement la partie.
Voila , si tu as des questions , ou si tu cherches un volontaire pour des test réseau ce week-end , pas de pb.
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Re: Thread et réseau

Message par blendman »

Merci g-rom pour ta réponse ;).

G-Rom a écrit :C'est faux , #MESSAGE_CORRECTION_COORDONNE à une taille de 1 , la prochaine écriture se fera à @Adresse + 1 ( 1 c'est l'offset )
Au temps pour moi, je croyais que c'était la taille demandé pas la position, merci ;).
tu te dois de maîtrisé les pointeurs à la perfection ;)
Fait toi une collection de fonction qui te permet de fabriqué des paquets facilement , ca t'évitera pas mal d'erreur par la suite :

du style :
CreatePacket.i(Header.a)
PushFloat(*Packet.sPacket, float.f)
PushInteger(*Packet.sPacket, integer.i)
...
PopFloat.f(*Packet.sPacket)
PopInteger.i(*Packet.sPacket)
...
les pointeurs, je ne maitrise toujours pas ^^.
je vois à peu près à quoi ça pourrait ressembler.


Ton message peu avoir une grosse taille , pas de pb , tu rajoute un flag de 4 byte après l'entete :
ah ben oui, j'aurai du y penser tout seul.
J'imagine que l’intérêt de connaitre la mémoire c'est pour pouvoir tout récupérer au cas où on n'a pas fait une allocation mémoire suffisante.
ton jeu fabrique des paquets en temps réel , pas dans la boucle bridé par l'affichage ( bridé par le FPS j’entends )
chaque paquet fabriqué part dans une liste chainée , tout les 25ms ( fait tes test de perfs pour paufiné le temps idéal avec des vrais clients distant ( je serais dispo ce week end si tu veut pour des tests ) tu balances les paquets partout , coté client & serveur donc , tout le monde vide sa pile. ( ca c'est les message important )

Tout les 100 ms , tu fait les corrections de syncro ( idem pour le temps , a faire des test aussi ) , la syncro concerne principalement les coordonnées , l’envoie de texte pour le tchat , l'envois de partie de fichier , etc... , le but est de ne jamais saturé la bande passante , crois moi ça va vite...
& tu écoutes en temps réel l'arrivé de nouveau paquets.
si ton programme est bien ficelé , tu n'auras pas besoin de thread, car si ils sont mal utilisé , ton réseau sera bancal.
je te conseil de te faire une collection de fct° dans des fichiers séparé ( sa pourrais te resservir dans d'autres projets d'ailleurs , faire une lib... ) bref, faire une couche d'abstraction supplémentaire à la lib network de pb.
si tu as compris tout ce que je t'ai dis , ton réseau devrais roulé comme du tonnerre de Zeus !
point clé , alléger un max les paquets , bien temporiser l’envoi tout les 20/25/30ms pour les message important , 75/100/150ms pour le reste.

ha oui , chaque client devras envoyé au serveur tout les 500ms un "I_AM_ALIVE" (je suis en vie)
si le serveur ne reçois pas ce message tout les 500 max , le serveur considéra comme déconnecté le client.
c'est une sorte de ping , ça permet surtout d'alléger la charge du serveur si un ou des joueurs n'ont pas quitté correctement la partie.
Voila , si tu as des questions , ou si tu cherches un volontaire pour des test réseau ce week-end , pas de pb.
En fait, je comprends l'idée et je vois globalement ce qu'il faut faire :
- les paquets : ce sont des allocate_memory() et série de poke c'est ça ?
- la pile : c'est une liste chainée à laquelle on ajoute les paquet. J'attribue un paramètre importante = 1 pour les messages à envoyer tous les 25ms et importance=0à envoyer tous les 100 ms.
- ensuite, je mets un timer (un autre paramètre de la liste chainée) et toutes les 25ms, j'envoie les paquets importantes, et tous les 100 ms j'envoie les autres, à chaque en supprimant les éléments de la liste

En gros, cela correspond -il à ce genre de chose, ou je suis complètement à coté ? :)

Code : Tout sélectionner

;{ infos
; SERVER
; PB 4.61
; Date : 03/02/2012
; Blendman
;}

;{ enumeration , constantes et variables
#MESSAGE_CONNEXION = $0
#MESSAGE_LOGIN_MDP = $1
#MESSAGE_CHOIX_PERSONNAGE = $2
#MESSAGE_COORDONNEES = $50
#MESSAGE_DECONNEXION = $FE
#MESSAGE_MOBMOVE = $64
#MESSAGE_MOBATTACK = $65
#MESSAGE_MOBDIE = $66
#MESSAGE_TCHAT = $96

;}

;{ structures
Structure Stplayer
  pseudo$
  mdp$
EndStructure
Global player.Stplayer

Structure StPacket
  id.w
  size.w
  importance.a
  *message
  timer.w
EndStructure
Global NewList packet.StPacket()

Structure stClient
  id.i
  nom$ : pass$
  status.a; en attente, connecté...
  NumMap.w
  x.w : y.w
  peuple.a
EndStructure
Global NewMap Client.StClient()

;}

;{ procedures

Procedure PushFloat(*Packet.StPacket, float.f)
  PokeF( *Packet+4 , float)
EndProcedure

Procedure PushInteger(*Packet.StPacket, integer.i)
  PokeF( *Packet+4 , integer)
EndProcedure

Procedure.i CreatePacket(Header.a)
  AddElement(packet())
  With packet()    
    Select Header
        ; pour client ce qui suit
;       Case #MESSAGE_CONNEXION
;         \size = 1+2
;         \message= AllocateMemory(\size); on lui envoie son id
;         PokeA(\message, Header)
;         PushInteger(\message, IDplayer)
;         \timer = 25
;         
;       Case #MESSAGE_LOGIN_MDP
;         \size = 1+2+Len(player\pseudo$+player\mdp$)
;         \message= AllocateMemory(\size)
;         PokeA(\message, Header)
;         \timer = 25
;         
;       Case #MESSAGE_CHOIX_PERSONNAGE
        
      Case #MESSAGE_COORDONNEES
        \size = 1 + (2 * 4)
        \message= AllocateMemory(\size); que x et y comme direction en float.
        \importance = 1
        PokeA(\message, Header)
        PushFloat(\message, posX)
        PushFloat(\message, posY)
        \timer = 25
        
        
      Case #MESSAGE_DECONNEXION
        \size = 1+2
        \message= AllocateMemory(\size); on lui envoie son id
        PokeA(\message, Header)
        \timer = 25
        
      Case #MESSAGE_TCHAT
        \size = 1+2
        \message= AllocateMemory(\size)
        PokeA(\message, Header)
        \timer = 25
        
      Case #MESSAGE_MOBMOVE
        \size = 1+2*4
        \message= AllocateMemory(\size)
        PokeA(\message, Header)
        \timer = 100
        
    EndSelect
  EndWith
EndProcedure
;}

;{ init
If InitNetwork() = 0 : End : EndIf
If OpenConsole() =0 :  End : EndIf
;}

If OpenNetworkConnection("127.0.0.1",6832, #PB_Network_TCP) 
;{ boucle 
Repeat
  keypress$ =Inkey()  
  
  ForEach packet()
    If packet()\timer <= 0   
      If packet()\importance = 1
        ForEach client()
          SendNetworkData(client()\id,packet()\message,packet()\size)
        Next 
        DeleteElement(packet(),1)        
      ElseIf packet()\importance = 0
        ForEach client()
          SendNetworkData(client()\id,packet()\message,packet()\size)
        Next
        DeleteElement(packet(),1)
      EndIf
    Else
      packet()\timer-1
    EndIf   
  Next
  
Until keypress$ = Chr(27)
;}

EndIf

Encore merci :).
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: Thread et réseau

Message par G-Rom »

En gros, cela correspond -il à ce genre de chose, ou je suis complètement à coté ?
Tu as l'air de m'avoir compris.
Procedure.i CreatePacket(Header.a)
AddElement(packet())
Quand je vois ce code , cela veut dire que tes fonctions de gestion de paquets sont "collés" au moteur d'envois de paquet.
pour ma part , je fragmenterais encore plus le code, d'une part , c'est plus lisible , et tu pourras réutilisé tes paquets pour autre chose.
Tu créer le paquet, dans la foulé tu le fou dans une liste , c'est pas bon, ca fait des listes globale , des variables globale , bref , c'est pas propre, ca rend ton code
difficile à maintenir.

Ta fonction CreatePacket doit retourné un pointeur de paquet.
ex:

Code : Tout sélectionner

Procedure.i CreatePacket(Header.a)
   *paquet.sPacket = allocatememory(sizeof(sPacket))
   ..
   ..
   ..
 procedurereturn *paquet
endprocedure
une fois que tu as fini de faire des push , tu peut le balancer dans une liste , une liste pour les envois à 25ms une autre pour les envois a 100ms.

Voici un exemple de la partie qui s'occupe de recolté les paquets et de la gestion de l'envois.

Code : Tout sélectionner

#NETWORK_PACKET_PRIORITY_LOW  = $FF
#NETWORK_PACKET_PRIORITY_HIGH = $EE 

Structure NetworkEngine
  
  List _packet_a.i()
  List _packet_b.i()
   
  
EndStructure


Procedure PushPacket(*network.NetworkEngine, *packet.sPacket, priority.w)
 
  Select priority
    Case #NETWORK_PACKET_PRIORITY_LOW  
      AddElement(*network\_packet_a())
        *network\_packet_a() = *packet
    Case #NETWORK_PACKET_PRIORITY_HIGH
      AddElement(*network\_packet_b())
        *network\_packet_b() = *packet
  EndSelect
  
EndProcedure


; example :
NetworkGame.NetworkEngine

PushPacket(@NetworkGame, *tonPaquet, #NETWORK_PACKET_PRIORITY_HIGH)

N'oublie pas, pour un gros projet , sépare bien les tâches , évite un maximum de dépendance entres les fichiers, c'est à dire que la partie network , en dehors de ton code de gameplay , dois être autonome , c'est à dire , que tu dois pouvoir réutilisé ton code dans un autre projet sans modification , ne mélange pas le chargement des son & le réseau... sépare bien les tâches.


Le réseau doit comprendre au moins deux grandes partie :

- Un fichier pour la gestion/création de paquet
- Un fichier "noyau" qui gère les envois de paquet

bien sur tu peut encore étendre les fichiers , comme par exemple , un juste pour la gestion des bans , un autre pour les messages interne au moteur , etc...
plus tes fichiers sont simple & petit , plus facile sera la correction de bug par la suite, surtout si tu remet le nez dans le code quelques mois plus tard ;)
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Re: Thread et réseau

Message par blendman »

G-Rom a écrit :Ta fonction CreatePacket doit retourné un pointeur de paquet.
ex:

Code : Tout sélectionner

Procedure.i CreatePacket(Header.a)
   *paquet.sPacket = allocatememory(sizeof(sPacket))
   ..
   ..
   ..
 procedurereturn *paquet
endprocedure
ok, mais pour créer plusieurs paquet, je dois donc faire comment ?

Comme ça ?

Code : Tout sélectionner

*packet1 = CreatePacket(#MESSAGE_1)
*packet2 = CreatePacket(#MESSAGE_2)
*packet3 = CreatePacket(#MESSAGE_3)
;etc... 
une fois que tu as fini de faire des push , tu peut le balancer dans une liste , une liste pour les envois à 25ms une autre pour les envois a 100ms.
Voici un exemple de la partie qui s'occupe de recolté les paquets et de la gestion de l'envois.
Merci pour l'exemple. Je vais y réfléchir. J'avoue que même si je comprends globalement comment ça marche je ne vois pas encore trop comment tout ça s'agence, et comment je dois organiser le code.
N'oublie pas, pour un gros projet , sépare bien les tâches (...) plus tes fichiers sont simple & petit , plus facile sera la correction de bug par la suite, surtout si tu remet le nez dans le code quelques mois plus tard ;)
c'est ce que j'essaie de faire ;).
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: Thread et réseau

Message par G-Rom »

ok, mais pour créer plusieurs paquet, je dois donc faire comment ?

Comme ça ?

Code : Tout sélectionner

*packet1 = CreatePacket(#MESSAGE_1)
*packet2 = CreatePacket(#MESSAGE_2)
*packet3 = CreatePacket(#MESSAGE_3)
;etc...
bingo ;)
Répondre