|
Publicité ' | |||||||||||||||||||||||
|
|
#1 | ||
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Bonjour,
tout nouveau dans le réseau j'aimerais créer une application Client/Serveur. La partie sockets, connection etc est gérée mais il me manque l'essentiel : la "norme" de communication entre mes clients et mon serveur, comment se comprenne t-il ?Alors j'ai pensé rapidos à une structure de message, dites-moi honnêtement si c'est nul. ![]() Je construit la trame à partir de suite d'entier signé codé sur 16 bits, suivit d'une suite de paire<entier_signé_16_bits, valeur_string>. Ce qui donnerais une trame comme: entier_signé_16_bits entier_signé_16_bits... [entier_signé_16_bits string entier_signé_16_bits string] Le premier entier serait la cible du message, j'en vois deux pour l'instant : - account : tout ce qui concerne les données de l'utilisateur dans l'application en général (mot de passe etc) - game : toutes les données qui concerne le jeu (position, score etc) Le deuxième entier serait l'objectif du message : - info : donne une info, comme ca gratuitement sans rien attendre en retour (exemple position des adversaires) - ask : questionne, demande un retour d'information - answer : répond à une question Le troisième entier serait soit la question posée, soit une action. Enfin le reste de la trame renseignerait les potentielles données necessaire à sa compréhension sous la forme <type de l'information (entier signé 16 bits), valeur string> Code :
Demande des identifiants : serveur > CONCERN_ACCOUNT PURPOSE_ASK REQUESTS_ASK_ACCOUNT_IDS client > CONCERN_ACCOUNT PURPOSE_ANSWER REQUESTS_ASK_ACCOUNT_IDS TYPE_USERNAME user_toto TYPE_PASSWORD password_toto Changement de position : client > CONCERN_GAME PURPOSE_INFO REQUESTS_INFO_MOVE TYPE_POS_X 50 TYPE_POS_Y 455 Bien sûr il faut gérer les incohérences, des messages comme ci-dessous serait sans-sens. CONCERN_ACCOUNT REQUESTS_INFO_SHOOT TYPE_USERNAME user_toto Enfin bref, je sais très bien que ce n'est pas une bonne solution, parce que ca me parait complexe de prévoir tous les cas dès le début, et de plus je trouve qu'il y a pas mal de traitement derriere pour comprendre le message. Par contre il est vrai que ce genre de msg ne pèserais que qq octets Comment faîtes-vous ? |
||
|
|
00
|
|
|
#2 |
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Hello,
tu trouveras peut-être quelques idées dans ce post (et les suivants). Le dernier point (qui commence par "faire un niveau d'abstraction au dessus de ConnexionPool pour pouvoir séparer les communications en 'canaux' logiques") est notoirement intéressant, car il te permettra de séparer tes messages en canaux logiques, et donc de pouvoir 'décentraliser' la gestion de tes messages en les regroupant par 'thème', et donc en les gérant dans des endroits dédiés de ton code, ce que je détaille ci-après: concrètement ta structure de message ressemblera à ça: [taille] [no_canal] [type_message] [donnees_message] - [taille] représente la taille totale du message (en octets) - [no_canal] représente le no. de l'entité qui va s'occuper de traiter ledit message - [type_message] renseignera sur l'action elle-même a opérer et déterminera le contenu à proprement parler de donnees_message - [donnees_message] contiendra les données nécessaires au traitement de l'action (par exemple, pour tel type de message, ce sera 2 entiers, trois string et deux float). Et au niveau du code, tu auras différentes couches: - la classe TcpframeSocket qui s'occupe de gérer la socket bas niveau, de récupérer les octets reçus et de les découper en 'messages' (grâce à [taille]) pour constituer une instance de TcpFrame. Quand un événément s'est produit (genre 'un nouveau message est disponible') envoie des 'signaux' (pattern observer) à qui a envie de l'écouter. - la classe ChannelManager, qui contient une liste de 'Channel' qui s'y seront enregistrés ; c'est elle qui écoute la TcpFrameSocket et quand un message est disponible, elle va décoder le numéro de canal concerné contenu dans la TcpFrame (champ [no_canal]) et lui refiler le message à traiter. - les classes dérivant de 'Channel' qui auront donc une méthode qui sera appelée quand un message qui leur est destiné a été reçu et doit être traité. Ce 'channel' (par exemple AuthentificationChannel) va décoder le type de message, en déduire le nombre et le type de données contenues dans 'données-message', les récupérer et faire le traitement approprié. Par exemple côté serveur, le message de type 'LOGIN' envoyé par le client contient deux string: pseudo & motDePasse. Ils serviront à récupérer les infos de l'utilisateur dans une base de données ; une fois la chose faite, ce channel pourra renvoyer au client un message de type LOGIN_ERROR, ou PASS_ERROR, ou LOGIN_OK, chacun avec ses données propres. L'avantage c'est que tu peux ainsi bien séparer chaque 'thème' de responsabilités dans des classes différentes, chacune dérivant de la classe 'Channel' de base. Par exemple si tu fais un jeu en ligne, tu auras un channel dédié à la réception/envoi des messages d'authentification, un autre channel dédié à la gestion des messages relatif au 'chat', un channel dédié aux messages relatifs aux actions des joueurs dans le jeu, etc... Il ne te restera alors plus qu'à faire une classe dérivée pour tel Channel côté client, et une classe pour ce même Channel côté serveur. En comparaison avec ton idée, on a: - "premier entier" est une sirte d'équivalent à ma notion de 'channel' - 'troisième entier' est une sorte d'équivalent à ma notion de 'type de message' - 'deuxième entier' n'existe plus car ce sont les channels qui connaissent les types de message qui sont amenés à leur être adressé, et donc si tel type est un message 'spontané', une demande ou une réponse à une demande. Poru reprendre l'exemple d'avant, le code côté serveur dans AuthChannel sait que quand il reçoit un message de type 'LOGIN' il devra répondre quelque chose (LOGIN_OK, LOGIN_ERROR, ...). De même le code du ClientAuthChannel sait lui-aussi que lorsqu'il a envoyé 'LOGIN' il doit s'attendre à recevoir quelque chose par le serveur. Donc pas besoin de le coder dans le message, c'est implicite pour chacune des parties: client & serveur. - 'le reste' est un équivalent à mes [donnees_message]. Pour la sérialisation à proprement parler du contenu, il n'est pas nécessaire de coder le type de chaque valeur avant la valeur elle-même ; à nouveau le type de message détermine à lui seul ce que contiendra donnees_message, c'est implicite pour chacune des parties: client & serveur.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|
|
10
|
|
|
#3 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Merci beaucoup Nounouk ! (je savais que t'allais passer par là à l'occaz
D'ailleurs, j'ai implémenté toute l'architecture que tu proposes dans l'autre lien que j'avais déjà repéré. Faut dire qu'avec Qt c'est rapide Tout implémenté excepté les channels justement. C'est donc par ca que je vais commencer. Je reviendrai donner des nouvelles sur ce post (très bientôt je pense). Encore merci. edit: juste une question qui me vient en tête en relisant, [données_message] serait une succession de "paramètre" disons. Pas besoin de renseigner les types de chaque données, en effet on sait que pour tel type de message on attend 2 int et une string par exemple. Ca peut paraître bête mais comment les séparer ces données dans données_message ? Parce que pour des int on peut se mettre d'accord sur une taille. Mais pour une string ou char*, des informations pourrait prendre 2 caractères et d'autres une centaine. Donc donner une taille fixe pour ttes string parait inadapté... Comment qu'on fait ?
|
|
|
00
|
|
|
#4 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Autre question
Je t'expose premièrement où en est mon archi... TcpServer: - nouvelle connexion, informe connexion pool. ConnexionPool: - stocke la socket, l'associe à un UserDetails. - câble le signal disconnected() de la socket (j'utilise Qt pour rappel) pour être au courant de la déconnexion. - câble le signal frameReceived(TcpFrameSocket, TcpFrame) de la TcpFrameSocket à ChannelManager. ChannelManager: - stocke dans une Map<no_channel, Channel*> les channels. - la méthode onFrameReceived(TcpFrameSocket, TcpFrame) effectue un frame.getChannel(). Si le numéro existe dans la Map, on fait un map.value(channel_id)->treatFrame(frame). Channel: - Déserialize la trame, check si elle est correct, puis cohérente. Alors là tout le chemin de réception est "géré". Mais une fois la frame traitée, dans certains cas il faudra répondre (au client qui a envoyé la frame, ou à tous les clients dans le cas d'une mise à jour globale). Ma question est, quelle classe gère l'envoi ? Comme ca je dirais ConnexionPool car elle est la seule a possèder ttes les sockets clientes. J'imagine par contre que c'est Channel qui construit le message de réponse. Dans ce cas pour ne pas perdre l'information de "qui à envoyer le message", il faut que je trimballe la socket ou le userDetails associé de ConnexionPool à Channel puis de Channel à ConnexionPool ?... Merci beaucoup pour toutes mes questions |
|
|
00
|
|
|
#5 | |||||||||||||||||||||||
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Citation:
Après, c'est de l'ordre du détail. Perso, pour des raisons historiques, mon ConnexionPool et ChannelManager sont en fait très liées puisque ... c'est une seule et même classe. Si c'était à refaire, j'opterais probablement pour deux entités séparées avec un mécanisme d'observer (= signal/slot en Qt). Par contre, ton connexion Pool doit passer, en même temps que ta TcpFrame reçue, l'user qui y est rattaché (qui sera ensuite propagé aux Channel, qui saura ainsi de qui vient le message). (signal) ConnectionPool::frameReceived(TcpFrame& frame, User& user) (slot) ChannelManager::onFrameReceived(TcpFrame& frame, User& user) D'une façon globale, le channel va manipuler des TcpFrame reçues/envoyées et des (collections d') User qui y sont rattachés, tout le reste est abstrait par le plus 'bas niveau' (ConnexionPool, TcpFrameSocket). Citation:
Donc ton onFrameReceived ressemblera peu ou prou à ça: Code :
Citation:
Code :
Code :
Citation:
Channel expose des fonctions protected à ses classes dérivées pour envoyer des messages à un ou plusieurs User: sendMessageToOne(msgType, frame, user) ; sendMessageToList(msgType, frame, QList<User>). Ces fonctions ne font que passer le relais au ChannelManager dans lequel a été enregistré ce Channel. Pour cela, mon Channel a un membre protégé (ChannelManager* ownerChannelManager) auquel on affecte le ChannelManager au moment où on fait un registerChannel() Code :
Code :
Code :
Code :
Voilà pour la première approche 'en cascade': en résumé, le Channel dérivé utilise un appel à sendMessage de la classe abtraite Channel. sendMessage ajoute le messageType et passe le relais à son ChannelManager. ChannelManager ajoute le channelId et passe le relais à ConnexionPool. ConnexionPool retrouve la socket assocée au User et fais un socket->sendFrame(). Petite digression: au passage, on est en plein dans le principe d'encapsulation et d'empilement des protocoles du modèle réseau OSI: chaque couche (physique, transport, ... application) encapsule les données de la couche précédente et ajoute ses propres données de contrôle (ici un msgType, un chanId, ...). L'autre approche pour envoyer les messages est de dire que la socket est intimement liée aux instance de User. On peut donc ajouter dans la classe User un pointeur (protégé) vers la TcpFrameSocket et rendre User et Channel 'friend' pour que le channel puisse directement appeler le sendMessage de la socket: Code :
Le désavantage est qu'on lie plus fortement chaque 'strate' (ConnexionPool, ChannelManager, Channel), mais ce n'est pas irrémédiable: les pointeurs 'owner' peuvent parfaitement être remplacés par des sauts via des signaux/slots. C'est juste un poil plus verbeux (et théoriquement un poil moins performant, mais en pratique c'est totalement négligeable). Citation:
Pour reprendre l'exemple du login, notre classe AuthChannel peut matinenant être complétée pour renvoyer une réponse au message 'LOGIN' reçu: Code :
- l'ajout du msgType par Channel dans la frame qui devient: ["LOGIN_OK] [yourUserDetails] - l'ajout du chanId (123) par ChannelManager qui devient: [123] ["LOGIN_OK] [yourUserDetails] - la transmission de la frame à la TcpFrameSocket qui va ajouter la taille totale du message(25 octets), qui devint [25] [123] ["LOGIN_OK] [yourUserDetails] - ce message est envoyé par notre QTcpSocket sur le réseau. - le client va recevoir ça et faire le chemin inverse: [25] va permettre de découper le message ; [123] va permettre au ChannelManager de retrouver la bonne instance de Channel, ["LOGIN_OK"] va permettre à processMessage de trouver le bon traitement à faire, et ledit traitement pourra récupérer son [yourUserDetails] pour faire ce qu'il a a faire. Enfin, pour revenir sur les User multiple, c'est aux Channel spécialisés de maintenir la liste des User à qui il peut être amené à envoyer des messages (ou pas s'il n'en a pas besoin). Par exemple un channel qui se contente de répondre à une requête PING d'un client (pour que le client puisse estimer la latence entre lui et le serveur) n'a pas besoin de maintenir de liste d'utilisateur puisqu'il ne fera que répondre au User associé à la TcpFrame qu'il reçoit. Même principe pour le channel dédié à l'authentification: il n'a pas besoin de connaître d'autres users hormis celui qui fait la demande. A contrario, un Channel d'un jeu qui gère le chat 'global' permettant à tous les joueurs connectés de converser entre eux aura besoin de maintenir une liste des joueurs actuellement connectés ; ainsi quand il recevra un message d'un joueur il pourra le relayer à tous les autres. C'est la responsabilité de la classe dérivée de Channel (GlobalChatChannel) de maintenir cette liste de user à jour, par exemple en écoutant les signaux de ConnexionPool pour être prévenu quand un User se connecte/déconnecte. La liste sera ensuite utilisée quand on voudra envoyer un message à tous les User connectés. En espérant que mon pavé indigeste reste à peu près compréhensible ... bon courage ![]() PS: désolé d'avance pour les erreurs de syntaxe qui ne manquent probablement pas dans mes bouts de code ; dur d'écrire sans faute du C++ à la volée dans un forum quand ça fait presque 4 ans qu'on pratique quasi exclusivement un autre langage (en l'occurrence, Java)
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||||||||||||||||||||||
|
|
00
|
|
|
#6 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Et je reviendrai surement avec d'autres questions ![]()
|
|
|
00
|
|
|
#7 | |||
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Citation:
Citation:
![]() Citation:
La petite cerise à propos de la gestion des listes d'utilisateurs dans les Channels, c'est typiquement un concept que tu vas réutiliser dans de nombreuses implémentations concrètes de channels, donc mieux vaut la factoriser ; fais une classe abstraite ChannelWithUsers, dérivée de Channel, qui contient cette liste et des méthodes registerUser(), unregisterUser(), getChannelUserList(), isUserInChannel(), sendMessageToAllUsers(), etc ... Tu pourras ensuite faire dériver de ChannelWithUsers tes channels qui en ont besoin.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||
|
|
00
|
|
|
#8 | ||
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Salut !
Il a l'air sympa ton jeu BounceBox, jtesterai sur la freebox Je reviens avec quelques questions qui sont apparues ce matin dans ma ptite tête pas réveillée La première concerne la TcpFrame. Tu dis quelle peut se comporter comme une QQueue, je pense que tu voulais dire un QStack Dans Channel dérivé on fait genre : frame.pushString(QString("user)); frame.pushString(QString("password"); frame.pushInt(LOGIN); // type message Dans ChannelManager: frame.push(channel_id); Dans TcpFrameSocket: frame.push(taille); Pour la réception haut niveau on fait le même principe avec des pop() sur le stack. Tout ca tu me l'a bien expliqué. Mais comment on passe de ce stack haut niveau en un bytes array et inversement d'un bytes array à un stack ? Parce qu'à un moment il faut bien que je fasse un socket.write(...), et cette fonction n'accepte pas de stack Aussi comment être sur de l'intégrité de la frame: si lorsque que je lis la frame j'effectue un popString() sur la donnée "123". 123 peut être caster en int comme en string sans erreur... et pourtant une string 123 pourrait ne pas avoir de sens. La deuxième question concerne l'archi de l'appli. Code :
Si LOGIN_OK, as-tu une classe qui associe un UserDetails à un Player (qui lui reprend les infos existante de la BDD, pseudo etc) ? Pour reprendre ton idée de ChannelWithUsers, dans la plupart des cas la liste d'users serait en fait la liste de players (user connecté et authentifié), non ? Enfin si LOGIN_FAILED, est-ce au serveur de déconnecter la socket du client, ou au client de déconnecter sa socket à la réception de ce message ? Voilà encore pas mal de questions ![]() A bientôt et merci |
||
|
|
00
|
|
|
#9 | |||||||
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Citation:
Si côté client je fais addInt(A); addInt(B); addInt(C), je veux pouvoir les récupérer dans le même ordre de l'autre côté (A,B,C), pas dans l'ordre inverse (C,B,A). C'est donc bien une FIFO. Citation:
Cf. mon post précédent, paragraphe "Et l'appel à sendMessage va déclencher [...]": la TcpFrame est d'abord peuplée par le code applicatif (yourUserDetails) puis on ajoute en début de frame [msgType], pûis [chanId], puis [taille]. En Qt, la QQueue est très proche d'une QList (dont elle dérive d'ailleurs). Et dans QList, tu as justement les fonctions append() et prepend() qui sont déja dispo et dont tu peux t'inspirer. Citation:
A noter que Qt propose aussi QDataStream pour lire/écrire directement les types courants supportés par QVariant depuis/vers un QByteArray, ce qui devrait te simplifier encore plus l'implémentation de Tcpframe. Citation:
Au pire, si tu veux un mode 'debug' pour t'assurer pendant le développement et les tests que tu ne te plantes pas en essayant de récupérer un string au lieu d'un int, tu peux faire une version spéciale de TcpFrame, TcpFrameDebug qui, pour chaque valeur ajoutée dedans, ajoute en fait deux champs au lieu d'un: le premier renseignant le type et le second la valeur elle-même. Et ta TcpFrameDebug sera capable de lever une exception si tu fais un getMonTypeA() alors qu'elle constate que la prochaine valeur à lire est de type B. Mais je déconseille d'utiliser ça en 'release', mieux vaut limiter son utilisation en debug, car ça introduit une belle quantité d'overhead totalement inutile une fois que le code applicatif est testé. Citation:
Pour reprendre ton exemple, si l'utilisateur n'est pas encore authentifié, tu peux parfaitement le stocker dans un booléen de ton UerDetails ; quand un message est reçu par un channel, il peut décider de vérifier si l'utilisateur est authentifié avant de prendre en comtpe le message. A noter que le cas de l'authentification est un peu spécial ; puisque logiquement tu ne veux pas que ton client ait la moindre chance d'accéder à tes channels tant qu'il n'est pas dûment authentifié, tu peux faire a en deux étapes: au lieu que toute socket qui vient de se connecter soit automatiquement et immédiatement enregistrée dans le ConnexionPool, tu vas d'abord la refiler à une classe AuthManager séparée (et donc en dehors de toute notion de ConnexionPool, de Channel, etc...). Elle se chargera de l'échange des messages entre le serveur et le client pour l'authentification. Une fois l'authentification dûment faite, c'est seulement là que tu peux enregistrer ta socket et ton UserDetails (peuplé avec les infos de ta BdD par exemple) dans le ConnexionPool. Citation:
A noter que ta classe ChannelWithUsers peut parfaitement décider de systématiquement éliminer tout message entrant qui ne viendrait pas d'un user enregistré dans sa liste de user déclarés dans le channel. Citation:
A nouveau, tout cela dépend de la façon dont tu veux que ton code applicatif se comporte. Ce n'est pas de la reponsabilité du framework réseau de prendre ce genre de décision ; lui n'a pour but que de proposer une API simple et générique pour faciliter l'écriture du code applicatif au dessus.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|||||||
|
|
00
|
|
|
#10 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Encore une fois réponse très détaillée et rapide !!!
Merci
|
|
|
00
|
|
|
#11 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Je vais en effet reprendre cette notion d'AuthManager qui se place entre TcpServer et ConnexionPool, je trouve ca propre
![]() Ca sera mon AuthManager qui en cas de LOGIN_OK instanciera un Player avec les infos de la BDD, et passera à ConnexionPool le couple <Player, Socket>. En prenant garde de se désabonner de tout évènement de la socket, pour vraiment passer la main à 100%. Ah oui j'ai pensé renseigner la version du protocol utilisée à la place du channel_id de la trame pour la phase authentication, si le client n'a pas la bonne version, AuthManager renverra un joli BAD_PROTOCOL Donc pour l'instant, j'ai de quoi coder sur le serveur ![]() Juste un petit doute sur qui possède qui dans l'histoire mais c'est du détail puisqu'il n'existe pas de solution idéale. Je dirais que TcpServer possède Un AuthManager et Un ConnexionPool. ConnexionPool possède Un ChannelManager qui lui possède Plusieurs Channels. Je réfléchis maintenant au client... alors tu me diras ce que tu penses de ce qui suit. TcpClient possède Un TcpFrameSocket, Un AuthManager, Un ChannelManager et Un Player (vide avant l'authentication). TcpClient connecte les évènements de la socket à AuthManager au début, avant de reprendre le contrôle une fois authentifié (et l'objet Player construit) pour prendre le rôle du ConnexionPool dans le serveur (redirection des frames entrantes au ChannelManager, envoi des frames provenant de ChannelManager) puisque c'est le seul à avoir accès à la socket. Voili voilou |
|
|
00
|
|
|
#12 | ||
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Citation:
Attention également à bien blinder certains points pour éviter les hacks. Genre si ta taille de message est codée sur un int, ça peut paraître malin au départ. Mais si je veux jouer à l'emmerdeur, je peux me connecter sur ton serveur et j'envoie une fausse trame avec comme taille 2^32 octets (4Go) et du 'garbage' à n'en plus finir ensuite. J'aurai vite fait de faire tomber ton serveur pour cause de memory full: il n'en finira plus de bufferiser le message à concurrence de 4Go par connexion et fake message envoyé puisque tant qu'il n'a pas atteint la taille du message indiquée dans le premier champ, il bufferise avant de construire une TcpFrame. Dans ce cas là, une taille genre sur 2 octets sera plus prudente: au pire, on limite les message à 2^16, soit 65ko maximum. Citation:
A noter au passage qu'on n'a pas parler d'un point important: les déconnexions: - parfois, quand une connexion se perd, la socket de l'ordi en face met beaucoup, beaucoup de temps à le détecter. Pour éviter d'avoir des connexions fantômes, il faut mettre en place un mécanisme de 'keepalive': toutes les n secondes, chaque socket envoie un message (de taille 0 par exemple). Si l'entité en face n'a rien reçu au bout de n*2 secondes, elle considère que la connexion a merdé et déclare la socket comme déconnectée. - la déconnexion d'un client est un événement qui intéresse les couches 'hautes' (ie. code applicatif) de ton programme. Par exemple ton channel de Chat voudra pouvoir envoyer un message aux autres joueurs pour dire 'machin a quitté' ; ont Channel d'une partie en cours devra pouvoir saborder la partie, etc... C'est donc un événement qui doit être remonté par ton ConnexionPool (ou TcpClient) jusqu'aux couches en relation avec ton code applicatif (tes Channel) ; tu peux parfaitement suivre le même principe que les sauts de 'couche en couche' qu'on fait pour remonter un message reçu.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
||
|
|
00
|
|
|
#13 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Merci pour tte ces précisions
|
|
|
00
|
|
|
#14 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Salut salut,
je reviens ici pour un avis. Je pensais naïvement pouvoir réutiliser les classes de mon serveur pour le client notamment la chaîne ChannelManager, Channel, ChannelWithUsers etc. Mais je constate que coté client, toute cette chaîne est un peu diffèrente, me trompe-je ? Avoir des ChannelManagerServer/Client, ChannelServer/Client... parrait-il abérant ? Ou alors "bidouiller" les classes pour qu'elles implémentent des méthodes Server et Client. Je ne sais pas vraiment. Merci d'avance |
|
|
00
|
|
|
#15 | |
![]() ![]() Inscription : décembre 2006 Messages : 1 612 ![]() |
Citation:
Par contre, les classes de plus bas niveau (TcpFrameSocket, TcpFrame) ont vocation à être réutilisées à l'identique des deux côtés.
__________________
Mon projet du moment: BounceBox, un jeu multijoueurs sur Freebox, sur PC et depuis peu sur smartphone/tablette Android. |
|
|
|
00
|
|
|
#16 |
|
Candidat au titre de Membre du Club
![]() Étudiant Inscription : avril 2009 Messages : 57 ![]() |
Merci de me conforter dans mon idée
![]() Tu arrives à avoir un niveau d'abstraction entre la version Serveur et Client ? Je pensais au départ, mais vu le peu de chose en commun entre les 2 versions, channelID pour un Channel par exemple, je me pose des questions... |
|
|
00
|
Copyright © 2000-2013 - www.developpez.com