IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Threads & Processus C++ Discussion :

Problème MT sur sockets.


Sujet :

Threads & Processus C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre très actif
    Homme Profil pro
    Second de cuisine
    Inscrit en
    Avril 2005
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Second de cuisine
    Secteur : Alimentation

    Informations forums :
    Inscription : Avril 2005
    Messages : 193
    Par défaut Problème MT sur sockets.
    Bonsoir,

    Je code en ce moment un serveur TCP pour un MMORPG.
    Ayant comme idée en tête que même si je ne suis pas vraiment bon en C++, je veux une bonne synchronisation entre ce que un joueur enverra, et que tout les joueurs sur la même map recoivent ce que le serveur aura à transmettre, en même temps.

    Voici une esquisse (Picasso n'est-il pas?): http://imageshack.us/photo/my-images/194/37628850.png/

    Mon serveur a comme architecture:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    - LoginServer
    Ecoute les connections, il sert a ce que les joueurs s'identifie, gère les banIP, choix du World, et du channel, et de son personnage.
    - WorldServer
    Il contient X channel, et est unique (nom). Il recoit le client apres connection au login, pour alleger ce dernier (je pense ?)
    Il doit egalement gerer une PARTIE des packets recus par les clients
    - Channel Server
    Ce serveur gère le "in-game". Deplacements, attaques, blablatage.
    Chaque serveur, possede son propre thread.
    (Je pense que c'est une meilleure option que d'avoir 1 thread par client, car si jamais j'ai 5000 clients, je pense avoir un probleme, non ? Corrigez moi si c'est mieux d'avoir des milliers de threads, merci)

    Donc je reviens à mon image.
    Les clients vont envoyer des centaines de packets en meme temps.
    Comment est-il possible de faire, pour que certains packets soient gerer par Login, d'autres par world, et d'autres par channel ?
    Imaginons 1000 joueurs:
    • - LoginServer: 1000 joueurs, mais une gestion de TRES TRES peu de packets
    • - WorldServer: 500 joueurs (si j'en ai deux par exemple.), Avec plus de packets.
    • - Channel Server: 250 (2.) Ici, c'est un peu comme la centrale fukushima avec un tsunami en face.

    Exemple:
    • Je suis sur mn perso, je me deplace à gauche => Gestion par Channel X (X etant en fonction du channel sur lequel je me trouve)
    • Je quitte ma guilde => Gestion par World.
    • Je me deconnecte => Gestion par Login



    Voici le LoginServer, quand il démarre (JE n'ai fait aucun test de connexions, car je ne RECV pas encore sur le serveur.)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    XServer::LoginServer::LoginServer() {
    	running = true;
    	_nbClients = 0;
    	_acceptClients = true;
    	bool stop = false;
    	WSADATA WSAData;
        WSAStartup(MAKEWORD(2,0), &WSAData);
    	if((sock = socket(AF_INET, SOCK_STREAM, 0)) == (unsigned int)INVALID_SOCKET) {
    		cout << "ERROR: Could not start LoginServer" << endl;
    		stop = true;
    	}
    	SOCKADDR_IN sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(LS_PORT);
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR && !stop) {
    		cout << "ERROR: LoginServer couln'd bind socket parameters" << endl;
    		stop = true;
    	}
        if(listen(sock, 5) == SOCKET_ERROR && !stop) {
    		cout << "ERROR: LoginServer couln'd listen on port " << LS_PORT << endl;
    		stop = true;
    	}
    	if((Lthread = CreateThread(NULL, 0, ls_Listen, this,0, NULL)) == NULL && !stop) {
    		cout << "ERROR: LoginServer thread starting failed (ls_Listen)" << endl;
    		stop = true;
    	}
    	/*
    	if((Rthread = CreateThread(NULL, 0, ls_Recv, this,0, NULL)) == NULL && !stop) {
    		cout << "ERROR: LoginServer thread starting failed (ls_Recv)" << endl;
    		stop = true;
    	}
    	*/
    	if(!stop) {
    		cout << "INFO: LoginServer is up and listening on port " << LS_PORT << endl;
    	}
    }

    Et voici la fonction ls_Listen:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    DWORD WINAPI ls_Listen(LPVOID para) {
    	XServer::LoginServer *ls = (XServer::LoginServer *) para;
        SOCKADDR_IN csin;
        SOCKET csock;
        int size =  sizeof(csin);
        while(1) {
    		csock = accept(ls->sock, (SOCKADDR *) &csin, &size);
    		if(!ls->acceptingClients()) {
    			int a = CONNECT_REFUSED;
    			send(csock, (char*)&a, sizeof(int), 0);
    			shutdown(csock, 2);
    		}
    		else if(ls->getMaxClients() <= ls->nbClients()) {
    			int a = SERVER_FULL;
    			send(csock, (char*)&a, sizeof(int), 0);
    			shutdown(csock, 2);
    		}
    		else {
    			if(csock >= 0) {
    				printf("LoginServer: Client connection (IP: %s; Port: %d)", csin.sin_addr, csin.sin_port);
    				Client *c = new Client((std::string)inet_ntoa(csin.sin_addr), (int)ntohs(csin.sin_port));
    				c->Sock(csock);
    				ls->AddClient(c);
    				Packet *p = new Packet();
    				p->setInfo(1, 0);
    				c->writePacket(p);
    			}
    			else {
    				shutdown(csock, 2);
    			}
    		}
    	}
    }
    La ou je bug un peu, c'est que pour la fonction recv, il faut préciser le socket. Et je pense que c'est couteux de faire une boucle (de meme que cest trop couteux de demarrer 1000 threads.

    Donc, au final, j'aimerais pouvoir demarrer dans un premiers temps un thread dans LoginServer (ce qui est commenté) qui recevra les messages, sans pour autant faire une mega boucle sur 1000 connections.

    -------------------------------------

    Cette partie, est à oublier
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    				Client *c = new Client((std::string)inet_ntoa(csin.sin_addr), (int)ntohs(csin.sin_port));
    				c->Sock(csock);
    				ls->AddClient(c);
    Etant donné que j'avais pas encore réfléchi au probleme posé...

    Sans etre chiant (un peu quand meme), je ne suis pas vraiment bon. une expliquation pro = je vais rien comprendre


    Merci d'avance à ceux qui aurant le courage de m'aider

    nico.

  2. #2
    screetch
    Invité(e)
    Par défaut
    select() ou poll() sont des fonctions qui bloquent en attendant que quelque chose arrive sur une des sockets que tu leur donne

  3. #3
    Membre très actif
    Homme Profil pro
    Second de cuisine
    Inscrit en
    Avril 2005
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Second de cuisine
    Secteur : Alimentation

    Informations forums :
    Inscription : Avril 2005
    Messages : 193
    Par défaut
    Comment je peux utiliser ca sut 5-6 thread differents en prenant en commpte que mes socket se trouvent dans Clients::_clients?

  4. #4
    screetch
    Invité(e)
    Par défaut
    tu ne peux pas utiliser ca dans plusieurs threads sur les mêmes sockets, il faut une séparation de tes sockets. Je n'ai pas bien compris ton architecture, mais ton client (ta socket) ne peut être qu'a un endroit a la fois; soit c'est le LoginServer soit le WorldServer, etc. autrement il n'y a pas moyen que ca marche.

    Un exemple d'une architecture qui fonctionnerait mieux: un thread s'occuppe uniquement d'écouter les connections (les 1000 connexions, oui). il utilise donc listen() ou poll() et dés que quelque chose se passe sur une connexion, il fait ce qui suit:
    - reception du bourzouf dans un buffer
    - si le buffer est pas fini (en gros tu a recupéré la moitué de l'info) attendre
    - envoie le buffer dans une pile de choses a faire
    - passe au suivant

    ensuite, d'autres threads s'occuppent de traiter les demandes; a chaque fois qu'un buffer est empilé, un thread le dépile et fait le traitement correspondant, selon le type de paquet recu.

    ainsi:
    - tu maintiens un thread qui s'occuppe bien des connexions (en terme de réseau)
    - tu utilises tes threads au mieux; si personne ne déguilde, ton thread WorldServer a rien a faire. aussi, si tu as une machine avec plus de coeurs, tu crées simplement plus de threads qui vont traiter les paquets




    en règle générale on évite désormais les modèles multithreads ou les threads ont une tâche précise; on préfère les modèle "Workers" ou chaque thread peut faire n'importe quel type de job puis se rendormir, ce qui permet d'utiliser au mieux la machine.

    par contre tu vas avoir des problèmes d'accès multithreads aux resources partagées.

  5. #5
    Membre très actif
    Homme Profil pro
    Second de cuisine
    Inscrit en
    Avril 2005
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Second de cuisine
    Secteur : Alimentation

    Informations forums :
    Inscription : Avril 2005
    Messages : 193
    Par défaut
    Hmmm. Daccord. C'est une solution aussi.
    Donc du style:

    j'ai mon thread ls_Listen qui accepte les connexions.
    Il me faut un thread qui receptionne les données ? (*)
    Et ce dernier, en fonction du premier byte recu, ajoutera le packet dans (au pif.) une des 3 piles disponibles.
    Ces trois piles sont gérées alors par 3 threads differents, qui font le necessaire et renvoient le bordel aux autres clients, c'est ca ?


    (*) le thread:
    DWORD WINAPI ls_Recv(LPVOID para) {
    // il faudrait que para soit de type Packet *p. (qui est ma classe pour les messages (structure))
    // Comme ca, je parse de quoi ca parle, et je balances a mes autres potes thread.
    }

    Dans mon premier post, j'ai ma fonction/thread ls_Listen qui accepte les clients.
    Comment utiliser poll(), ou select(), dans cette fonction (Ou autre part dailleurs) pour que la performance soit optimale avec 1000 clients qui m'envoient des packet type echange IRC. Mais en beaucoup beaucoup plus rapide.

  6. #6
    screetch
    Invité(e)
    Par défaut
    tu es tres tres contraint par ton propre modèle objet, c'est pas forcément ce qui est le mieux

    donc en gros je sais pas si tu as regardé la fonction select (ou poll), si tu as pas regardé va voir avant de lire.

    en gros select va attendre qu'il se passe quelque chose sur une des sockets et te renvoyer quelle socket a un événement.
    Lorsque tu sais qu'il s'est passé quelque chose sur cette socket, tu vas pouvoir traiter la requete. La ou il y a un avantage c'est que tu SAIS que quelque chose c'est passé, je ferais donc ainsi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    for(ever):
    * select sur toute tes sockets
    * sur la socket qui a une activité: read
    * si tu as un paquet entier, mets la dans une file de paquets a traiter
    donc tu as un thread qui ne fait que select et lire le paquet, tu ne peux pas faire plus efficace dans un sens (si c'est trop de chsoes pour ton processeur, 'est simplement trop pour ton réseau aussi, y'a pas de miracle)

    ensuite tu as n threads (n > 1, preferable n = nombre de processeurs - 1) qui attende qu'un truc se passe dans la file (avec un semaphore)
    lorsque quelque chose est ajouté dans la file, ils le depilent, et font le traitement
    ces threads sont génériques, ils ne sont PAS des WorldServer ou des trucs comme ca. Ca n'a pas de sens d'avoir un WorldServer et un ChannelServer.
    Si la requete est une requete World, tu peux demander au WorldServer de la traiter; si tu as une requete de type Channel, tu peux demander au ChannelServer de la traiter (si ca t'amuse, même si je trouve que ca n'a pas forcément beaucoup de sens)

    la différence principale ici c'est que le WorldServer pourrait traiter deux requetes World au même moment sur deux threads différent. Ou plus, d'ailleurs.

    ensuite tu as un thread qui va juste attendre avec accept pour attendre de nouvelles connexions, celui la n'a pas grand chose a faire en général (il y a des joueurs qui entrent et sortent, sur les 1000, mettons 1 par minute...)



    ensuite

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 5
    Dernier message: 21/04/2011, 11h54
  2. problème sur socket
    Par chuko dans le forum Réseau
    Réponses: 7
    Dernier message: 06/09/2008, 20h19
  3. [CR8] Problème tableau sur plusieurs pages???
    Par christophe28 dans le forum SAP Crystal Reports
    Réponses: 5
    Dernier message: 02/11/2004, 15h46
  4. [MFC] Problème pointeur sur une classe
    Par mick74 dans le forum MFC
    Réponses: 7
    Dernier message: 14/04/2004, 14h17
  5. Notion sur Socket UDP
    Par oxor3 dans le forum Développement
    Réponses: 3
    Dernier message: 05/04/2004, 00h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo