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

Développement Discussion :

Thread principal bloqué après un appel à recv dans un thread distinct


Sujet :

Développement

  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    4
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2004
    Messages : 4
    Points : 2
    Points
    2
    Par défaut Thread principal bloqué après un appel à recv dans un thread distinct
    Bonsoir !

    Je m'en remet à vous après de longues heures de recherche et ceci depuis un peu plus d'une semaine pour résoudre un problème.
    Je developpe un jeu vidéo type rpg en ligne (du genre de ceux très prisés de nos jours) en C++, avec pour API graphique DirectX (natif), et la librairie Winsocks pour le réseau.

    Jusqu'à maintenant j'utilisait des sockets bloquantes ainsi que la fonction de multiplexage select afin de pouvoir envoyer mes requêtes au serveur et en recevoir les réponses de façon à ne pas bloquer le rendu graphique.
    Seulement, à moins que je ne me sois fourvoyé, il me semble qu'il faut appeler celle-ci avec une structure timeval correspondant à un intervalle de temps non nul afin de ne pas bloquer, soit au minimum à peu près 10 ms.
    Mon soucis avec cette solution c'est que ce temps accumulé à chaque appel de select, a un impact, même s'il est minime, sur le temps de boucle de mon programme.
    D'où ma première question, devrais-je négliger ce temps ou aborder le problème d'une autre façon ?

    Depuis j'ai essayé une autre méthode, effectuer l'envoi et la reception des données dans un thread distinct. C'est ici que réside mon plus gros soucis, une fois que l'utilisateur a entré ses identifiants je lance le thread qui effectue les opérations d'authentification. La fonction connect est appelée avec succès et ne bloque pas un poil le thread principal (de rendu), en revanche après l'envoi des identifiants, j'appelle la fonction recv afin de recevoir la réponse et cet appel bloque mon thread principal, et donc le rendu pendant un temps court (1 à 2 sec).
    Afin de m'assurer que le problème était bien du à l'appel de recv, j'ai rajouté un Sleep de quelques secondes à mon serveur entre la reception des identifiants et l'envoi de la réponse, on constate bien que recv bloque son propre thread (normal) et le thread principal donc pas de rendu pendant ce temps là =/

    J'espère avoir été le plus clair possible, dans le cas contraire ou s'il manque des indications, dites le moi, en attendant voici le code associé au problème.
    Merci d'avance à tous.

    Le client
    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    //La fonction principale du thread 
    DWORD WINAPI ConnectionThread(LPVOID lParam)
    {
    	t_client *m_client = (t_client*)lParam;
    	m_client->Connect();
    	return 0;
    }
    
    //La fonction appelée lorsque les identifiants de l'utilisateur ont été entrés lançant le thread
    void t_client::StartConnection(string username, string password)
    {
    	m_userinfo = username + ";" + password;
    	CreateThread(NULL, 0, ConnectionThread, this, 0, &m_thread);
    }
    
    //La fonction appelée par le thread
    void t_client::Connect()
    {
            //On récupère l'IP du serveur à partir du nom de domaine
    	struct hostent *host;
    	host = gethostbyname((char *)m_hostname);
    	SOCKADDR_IN sin;
    	sin.sin_addr = *((struct in_addr *)host->h_addr);
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(3000);
            //On créé le socket avec ces infos
    	m_socket = socket(AF_INET, SOCK_STREAM, 0);
    
            //On procède à la connexion du client
    	int success = 0;
    	success = connect(m_socket, (SOCKADDR *)&sin, sizeof(sin)); 
    	if (success == - 1)
    	{
    		m_connected = false;
    	}
    	else
    	{
    		m_connected = true;
    		//J'ai testé en mode non bloquant avec les deux lignes suivantes, et ça fonctionne
                    //u_long val = 0;
    		//ioctlsocket(m_socket, FIONBIO, &val);
    
                    //Envoi des identifiants au serveur
    		Send(m_userinfo);
    
                    //Reception de la réponse
    		s_data login;
    		login = Receive();
    		if (login.val.front() == "ok")
    		{
                            //L'authentification a réussi
    			m_authentificated = true;
    		}
    		else
    		{
                            //L'authentification a échoué, on de deconnecte
    			m_authentificated = false;
    			m_connected = false;
    		}
    	}
    }
    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    void t_server::ProceedConnection()
    {
    	SOCKADDR_IN sin;
    	int sin_size = sizeof(struct sockaddr_in);
            
            //On accepte le client
    	int client = accept(m_socket, (SOCKADDR *)&sin, (int *)&sin_size);
    	if(client != INVALID_SOCKET)
    	{
                    //Reception des identifiants de connexion
    		string user_info = "", username = "", password = "";
    		user_info = Receive(client);
    		int i = 0;
    		while ((i < user_info.length()) && (user_info[i] != ';'))
    		{
    			username = username + user_info[i];
    			++i;
    		}
    		++i;
    		while (i < user_info.length())
    		{
    			password = password + user_info[i];
    			++i;
    		}
    		cout << "\nDemande de connexion de ";
    		cout << username << endl;
    		int login = m_database->User_Login(username, password);
    		if (login)
    		{
                            //Le client existe, on effectue un sleep pour mettre en évidence le blocage puis on envoie la confirmation
    			Sleep(10000);
    			Send(client, "login;ok");
    			++m_nbClients;		
    			m_clients[m_nbClients - 1].name = username;
    			m_clients[m_nbClients - 1].desc = client;
    			cout << "Connexion acceptee" << endl;
    		}
    		else
    		{
                            //Les identifiants sont érronés, on envoie la réponse au client
    			Send(client, "error");
    			cout << "Connexion refusee, identifiants incorrects" << endl;
    		}
    	}
    }

  2. #2
    Nouveau Candidat au Club
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    1
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2008
    Messages : 1
    Points : 1
    Points
    1
    Par défaut
    tu devrais nous mettre la partie du code qui bloque...
    c'est à dire le thread principal.
    est-ce que par exemple tu attend m_authentificated ou un truc du genre ?
    pour tes attentes entre thread, je te recommande de te renseigner sur les semaphores.
    essaie de regarder si l'UDP (socket en mode non connecté / asynchrone) ça correspondrait peut être mieux pour gérer tes sockets.
    et enfin... je pense que les MMORPG c'est pas forcément le plus facile pour commencer.
    jette un oeil à XNA, ils ont peut être ajouté une gestion réseaux.
    Perso, je commencerais peut être avec un MMORPG 2D, avant de partir sur la 3D ça te permettra d'étudier le coté architecture sans t'emcombrer de toute les difficultés de la 3D tu feras le portage après.

  3. #3
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    4
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2004
    Messages : 4
    Points : 2
    Points
    2
    Par défaut
    Tout d'abord merci d'avoir prit le temps de me répondre!
    Il est vrai que j'aurais du poster le code du thread principal, le voici:

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    //Boucle de jeu
    void t_game::Login()
    {
    	t_timer::GetInstance()->Update();
    	m_keyboard->Update();
    	m_mouse->Update();
            //Interface de connexion gérant les appels aux fonctions de connexion
            //et d'authentification de la classe t_client
    	m_logon->Update();
    	m_intro->Update();
    	m_graphics->Render(m_gdi);
    	m_graphics->DoEvents();
    }
    
    //La classe de l'interface de connexion
    void t_gui_logon::Update()
    {
    	if (m_step == "input")
    	{
    		if (m_mouse->LeftPress())
    		{
    			if (m_focus != NULL)
    			{
    				m_focus->Unfocus();
    			}
    			m_focus = NULL;
    			if (m_username->Focus(m_mouse->GetPosition()))
    			{
    				m_focus = m_username;
    			}
    			if (m_password->Focus(m_mouse->GetPosition()))
    			{
    				m_focus = m_password;
    			}
    		}
    		if(m_focus != NULL)
    		{
    			m_focus->AddChar(m_keyboard->GetChar());
    		}
    		if (m_keyboard->GetKey(DIK_TAB))
    		{
    			if (m_username == m_focus)
    			{
    				m_focus->Unfocus();
    				m_focus = m_password;
    				m_focus->Focus();
    			}
    			else if (m_password == m_focus)
    			{
    				m_focus->Unfocus();
    				m_focus = m_username;
    				m_focus->Focus();
    			}
    		}
    		if (m_keyboard->GetKey(DIK_RETURN))
    		{
    			m_step = "checkinput";
    		}
    	}
    	if (m_step == "checkinput")
    	{
    		if ((m_username->GetText() == "") | (m_password->GetText() == ""))
    		{
    			m_msgbox->Open("\nVeuillez remplir tous les champs.");
    			m_msgbox->Show(true);
    			if (m_mouse->LeftPress())
    			{
    				m_msgbox->PressValidate(m_mouse->GetPosition());
    			}
    			if ((m_mouse->LeftRelease()) && (m_msgbox->ReleaseValidate(m_mouse->GetPosition())))
    			{
    				m_step = "input";
    				m_msgbox->Show(false);
    				m_username->Show(true);
    				m_password->Show(true);
    			}
    		}
    		else
    		{
    			m_step = "connect";
                            //Appel de la fonction de connexion non bloquante
                            //Le thread ne bloque pas dès l'appel de cette fonction
                            //mais lorsque celle ci appelle recv pour avoir la réponse du serveur
    			m_client->StartConnection(m_username->GetText(), m_password->GetText());
    		}
    	}
    	if (m_step == "connect")
    	{
            m_msgbox->Open("\nConnexion en cours...");
    		m_msgbox->Show(true);
    		if (m_client->Connected())
    		{
    			m_step = "auth";
    		}
    	}
    	if (m_step == "auth") 
    	{	
    		m_msgbox->Open("\nAuthentification...");
    		m_msgbox->Show(true);
    		if (m_client->Authentificated())
    		{
    			m_step = "success";
    		}
    
    	}
    }
    Ensuite m_authentificated permet à la classe de l'interface graphique d'afficher l'étape de connexion en cours.

    Je vais essayer de me pencher sur l'UDP tu as raison j'y trouverai surement ce que je cherche. Par contre j'ai vu de nombreux serveurs en TCP faire de simples appel à recv dans un thread pour avoir un fonctionnement asynchrone donc je ne comprend pas d'où vient ce blocage.

    Je tient à préciser que je ne suis pas vraiment débutant dans le domaine du jeu vidéo, j'ai un jeu d'aventure en 3D avec une partie réseau entre autres à mon actif (en .net, les classes TCPListener et TCPClient simplifiant grandement la chose pour l'asynchrone) et je maîtrise très bien DirectX.
    Mais tu as raison le développement du réseau doit commencer par une application simple, c'est pour cela que je n'utilise pas de 3D pour l'instant, je commence par définir un protocole avec l'interface de connexion et un chat en jeu (pour discuter dans un premier temps et ensuite implémenter des commandes pour attaquer par exemple avant de passer au concret).

    J'ai surement utilisé par emportement le terme MMORPG, car ce n'en est pas un mais ce n'est également pas un RPG en soi. Enfin bref un Multiplayer Online RPG, le but étant d'avoir un projet solide qui me permette d'apprendre et de montrer mes compétences dans tous les domaines du jeu vidéo (réseau, graphisme, 3D...) à des entreprises.

    J'ai longtemps hésité à passer à XNA et au C#, mais en parlant avec des professionnels du jeu vidéo, j'ai appris que la plupart d'entre eux utilisent le C++ et avec DirectX, et ne comptent pas changer cela avant les futures versions de DirectX. J'ai donc préféré utiliser les mêmes technologies, d'autant plus que j'ai déjà bien trop exploré le .Net à mon goût et ce changement vers le C++ natif me permet d'en apprendre beaucoup.

    J'espère que j'ai pu te permettre de comprendre le problème et merci encore une fois pour ton aide !

  4. #4
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    4
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2004
    Messages : 4
    Points : 2
    Points
    2
    Par défaut
    Bonjour à tous !

    Etant donné que je n'ai pas de réponse, je vais essayer de poser une question basique et simple histoire de voir si je part sur une fausse idée.

    Une façon d'utiliser des sockets bloquantes dans une application, sans bloquer le thread principal de celle-ci lors d'une reception par exemple, consiste simplement créer un nouveau thread et à y effectuer l'appel au fonctions potentiellement bloquantes ?

    Bon dimanche

  5. #5
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    4
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2004
    Messages : 4
    Points : 2
    Points
    2
    Par défaut
    Après un nombre considérable de tests divers et variés, j'ai enfin trouvé la solution (il y a environ une heure ) !!!

    Le problème se situait dans la fonction Connect() appelée par le thread secondaire, il comprenait toute la partie connexion au serveur à partir du socket, envoi des données d'utilisateur et enfin réception.
    Après plusieurs tests je me suis rendu compte qu'en limitant l'utilisation d'un thread à un unique appel d'une fonction bloquante des sockets, donc en faisant toute la partie traitement des données dans le thread principal et un thread différent pour chaque appel bloquant, tout fonctionne parfaitement bien.

    D'où une petite question, j'ai fait quelques recherches mais sans succès, mon problème se situait t-il dans le fait que mon thread secondaire effectuait plusieurs appels à des fonctions bloquantes de socket (un appel à send pour l'envoi des données, et un appel à recv pour la reception) ?

    Enfin bref, problème résolu ! Merci à tous.
    Patrick.

  6. #6
    la_tupac
    Invité(e)
    Par défaut
    Salut, c'est marrant ça j'ai planché la dessus y'a environ 3 mois et j'en suis venue à la même conclusion.
    Cependant, j'avoue ne pas avoir lu ton code (le mien me suffit a griller des fusibles),
    mais en cherchant une solution pour calmer mon serveur qui consome plus en ressources proc que ma bravo 16s en sp95 je suis tombé sur ce post:
    http://www.developpez.net/forums/d68...r/#post4479434
    il éxplique que la bonne solution pour le serveur reste le multiplexage.
    C'est démoralisant, j'avais déja avancé beaucoup sur mon protocole
    et il va faloir revoire toutes les fondations.

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

Discussions similaires

  1. Réponses: 1
    Dernier message: 04/06/2012, 16h10
  2. Client qui se bloque après plusieurs appelles serveur
    Par Takumi dans le forum Windows Communication Foundation
    Réponses: 4
    Dernier message: 15/02/2010, 01h20
  3. Réponses: 8
    Dernier message: 11/03/2009, 17h38
  4. [GLib] Interrompre un appel bloquant dans un thread
    Par Zorgblub dans le forum Réseau
    Réponses: 3
    Dernier message: 17/11/2007, 18h02
  5. appel asynchrone dans le thread principal
    Par mrrenard dans le forum C#
    Réponses: 6
    Dernier message: 05/04/2007, 09h07

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