Cours programmation réseau en C++

TCP - Mode non bloquant pour le client

Bousk

Voyons rapidement comment utiliser le mode non bloquant pour le client.

Commentez

- Titre : Cours programmation réseau en C++

- Auteur : Bousk

- Parution : 27 avril 2017

- Mise à jour : 10 juin 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Cyrille (Bousk) Bousquet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Mode non bloquant, select ou poll

- II. connect

I - Mode non bloquant, select ou poll

Afin d'utiliser un socket non bloquant, nous pouvons avoir recours au mode non bloquant, se servir de select ou de poll, qui ont une utilisation et mise en place identiques à celles que l'on a vues dans l'article précédent dans le cadre d'un serveur.

Quelle que soit la solution choisie, un avantage direct est de ne plus avoir besoin de threads pour traiter le réseau, éliminant ainsi le besoin de synchronisation, l'utilisation de mutex et tous les problèmes habituellement rencontrés dès lors que l'on parle de multithread. De plus vous aurez un meilleur contrôle des actions en cours, en pouvant interrompre à tout moment, par exemple une connexion - chose impossible auparavant.

II - connect

Pour un client TCP, connect pose un problème avec le mode non bloquant : bien que connect retourne immédiatement, comment alors savoir quand la connexion est réellement établie ? Ou a échoué ?

Dans ce cas, il faut coupler le mode non bloquant, qui est nécessaire afin que connect ne bloque pas, à l'utilisation de select ou de poll. Quand le socket est prêt en écriture, c'est que le processus de connexion est terminé. Il faut alors récupérer l'état du socket pour vérifier s'il y a une erreur, ou si tout s'est bien passé et la connexion est établie.

Comme pour les appels à recv et send non bloquant, connect retournera une erreur, il faudra alors vérifier de quelle erreur il s'agit : s'il s'agit de EINPROGRESS, c'est juste l'erreur liée au mode non bloquant, l'équivalent de l'erreur EWOULDBLOCK pour recv et send. Sinon il s'agit d'une vraie erreur survenue. Windows aime se faire remarquer et retournera EWOULDBLOCK suite à une erreur d'appel à connect non bloquant. Faites donc attention à ce cas particulier et si votre code est à vocation d'être porté, il faudra sans doute vérifier les deux valeurs.

connect non bloquant et poll pour vérifier le résultat

sockaddr_in server;
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr);
server.sin_family = AF_INET;
server.sin_port = htons(9999);
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Sockets::SetNonBlocking(client);
if (connect(client, reinterpret_cast<const sockaddr*>(&server), sizeof(server)) != 0)
{
int error = Sockets::GetError();
if (error != static_cast<int>(Sockets::Errors::INPROGRESS) && error != static_cast<int>(Sockets::Errors::WOULDBLOCK))
{
std::cout << "Erreur connect : " << error << std::endl;
return -2;
}
}
bool connected = false;
pollfd fd = { 0 };
fd.fd = client;
fd.events = POLLOUT;
while(true)
{
int ret = poll(&fd, 1, 0);
if (ret == -1)
{
std::cout << "Erreur poll : " << Sockets::GetError() << std::endl;
break;
}
else if (ret > 0)
{
if (fd.revents & POLLOUT)
{
connected = true;
std::cout << "Socket connecte" << std::endl;
break;
}
else if (fd.revents & (POLLHUP | POLLNVAL))
{
std::cout << "Socket deconnecte" << std::endl;
break;
}
else if (fd.revents & POLLERR)
{
socklen_t err;
int errsize = sizeof(err);
if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &errsize) != 0)
{
std::cout << "Impossible de determiner l'erreur : " << Sockets::GetError() << std::endl;
}
else if (err != 0)
std::cout << "Erreur : " << err << std::endl;
break;
}
}
}

if (connected)
{
//!< connexion établie avec succès
}

Si vous êtes familier avec le chapitre 1 et le chapitre 5, ce code ne devrait poser aucun souci de compréhension. Il s'agit de l'application directe de l'explication qui précède, avec réutilisation des codes des parties précédentes. poll peut être remplacé par select selon votre préférence. L'utilisation d'une boucle infinie pour attendre que la connexion soit établie n'est pas représentative d'un exemple réel, mais a juste pour but de montrer comment la logique est réalisée. Dans un code réel, il s'agira d'appeler ce code régulièrement, typiquement une fois par itération de la boucle principale du logiciel ou de la boucle de gameplay, afin de vérifier la progression de la connexion.

Article précédent

Article suivant

<< TCP - Envoi et réception depuis le serveur

TCP - Quelle architecture de client visée ? >>