0% ont trouvé ce document utile (0 vote)
34 vues30 pages

Network UDP08 DVP

Transféré par

lawrencetchamadeu2
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
34 vues30 pages

Network UDP08 DVP

Transféré par

lawrencetchamadeu2
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Cours programmation réseau en C++

UDP – Gérer des connexions entre machines

Par Bousk

Date de publication : 15 avril 2020

Dernière mise à jour : 26 août 2017

Dans ce chapitre nous allons mettre en place des mécanismes de gestion de connexion
et déconnexion.

Commentez
Cours programmation réseau en C++ par Bousk

I - Une connexion UDP ?!........................................................................................................................................... 3


I-A - Définir ce qu’est une connexion.................................................................................................................... 3
I-A-1 - Une relation asymétrique...................................................................................................................... 3
I-A-2 - Des échanges permanents…................................................................................................................3
I-A-3 - … de toute façon nécessaire................................................................................................................ 3
I-B - Définir une déconnexion................................................................................................................................ 3
II - Établir une connexion............................................................................................................................................ 4
II-A - Adresses de connexion.................................................................................................................................4
II-A-1 - Implémentation..................................................................................................................................... 4
II-A-1-a - Constructeur depuis une chaîne et un port................................................................................. 5
II-A-1-b - Constructeur depuis un sockaddr_storage.................................................................................. 5
II-A-1-c - Récupérer l’adresse IP sous format lisible.................................................................................. 6
II-A-1-d - Opérateurs de comparaison........................................................................................................ 6
II-A-1-e - Astuces et raccourcis : créer une adresse ANY..........................................................................7
II-A-1-f - Aller plus loin................................................................................................................................ 7
II-B - Interface de connexion..................................................................................................................................9
II-C - Demande de connexion................................................................................................................................9
II-C-1 - Refuser une connexion...................................................................................................................... 10
II-C-2 - Accepter une connexion.....................................................................................................................10
II-D - Message de connexion entrante................................................................................................................ 10
II-E - Mise en attente des données..................................................................................................................... 10
II-E-1 - Interface des protocoles..................................................................................................................... 10
II-E-2 - Gestionnaire de protocoles................................................................................................................ 11
II-E-3 - Client distant.......................................................................................................................................11
III - Déconnexion........................................................................................................................................................12
III-A - Comment se déconnecter ?.......................................................................................................................13
III-B - Processus de déconnexion........................................................................................................................13
III-C - Timeout.......................................................................................................................................................13
III-D - Datagramme de déconnexion....................................................................................................................14
III-D-1 - Gestion du datagramme de déconnexion......................................................................................... 15
III-E - Reconnexion...............................................................................................................................................16
III-F - Quand notifier la déconnexion................................................................................................................... 16
III-F-1 - Raison de la déconnexion................................................................................................................. 16
III-F-2 - Déconnexion ne devant pas être notifiée..........................................................................................17
III-G - Échec de connexion.................................................................................................................................. 18
IV - Maintenir une connexion.................................................................................................................................... 19
IV-A - État des lieux.............................................................................................................................................19
IV-A-1 - Keepalive...........................................................................................................................................19
IV-B - Types de datagrammes............................................................................................................................. 19
IV-C - Mise à jour de l’envoi................................................................................................................................20
IV-D - Keepalive connecté................................................................................................................................... 20
V - Diagramme des échanges de données.............................................................................................................. 21
VI - Tests....................................................................................................................................................................23
VI-A - Test de connexion..................................................................................................................................... 23
VI-B - Test du timeout.......................................................................................................................................... 26
VI-C - Timeout de connexion............................................................................................................................... 28

-2-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

I - Une connexion UDP ?!

Oui vous avez bien lu, nous allons dans ce chapitre mettre en place un système de connexion pour notre protocole.

La faisabilité est pourtant évidente : TCP, qui est également une surcouche d’IP, y parvient. Pourquoi un système du
même genre ne serait-il pas possible avec UDP, qui est un autre protocole par-dessus IP ?

Le système de connexion que nous créerons ici sera différent de ce qui se fait en TCP, mais la finalité est la même :
pouvoir maintenir différentes machines dans un état connecté, pouvoir accepter, refuser ou terminer une connexion
et bien sûr détecter une déconnexion.

I-A - Définir ce qu’est une connexion

Nous allons définir une connexion très simplement par la réception de données.

Tant qu’une machine B reçoit des données d’une machine A, alors, du point de vue de B, A est considérée connectée
à B.

I-A-1 - Une relation asymétrique

La définition précédente crée la connexion comme une relation asymétrique : tant que B reçoit des données de A, A
et B ne sont pas connectées l’une à l’autre mais, B considère que A est connectée à elle.

Afin que la connexion soit établie dans les deux sens, il faut alors également que A reçoive des données de B.

I-A-2 - Des échanges permanents…

Pour maintenir un état connecté entre deux machines il va donc falloir que celles-ci s’envoient régulièrement des
données en maintenant un flux d’échange permanent entre elles.

Et puisque UDP est sujet à des pertes par nature, cet échange devra être d’autant plus régulier pour y remédier.

I-A-3 - … de toute façon nécessaire

Mais en pratique ceci est généralement déjà le cas : vous avez probablement des données à envoyer à chaque frame
ou presque, comme des positions qui changent par exemple. Donc ces échanges permanents n’ont pas à être forcés.

I-B - Définir une déconnexion

Puisque l’état connecté se manifeste par la réception de données, alors une déconnexion sera tout naturellement
l’arrêt ou l’absence de réception de données pendant un certain temps.

Dans ce cas, chacune des machines peut initier une déconnexion : A peut décider de déconnecter B en arrêtant de
lui envoyer des données et B peut décider de se déconnecter de A en arrêtant de lui envoyer des données.

-3-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

II - Établir une connexion

II-A - Adresses de connexion

Souvenez-vous de la structure nécessaire à l’envoi ou la réception de données. Cette structure est peu évidente
à utiliser et surtout héritée du C : elle se manipule à grand coup de reinterpret_cast et de tampon interne. Nous
l’utilisions jusque-là faute de mieux, mais pour aller plus loin ce n’est pas acceptable pour l’utilisateur, et laborieux
pour nous.

Une des premières étapes sera donc de proposer un objet permettant de simplifier l’utilisation d’un couple adresse
IP/port.

Une adresse peut être créée à partir de plusieurs sources : une chaîne de caractères ou une structure C type
sockaddr_storage. D’autres supports de sources pourront être ajoutés au fur et à mesure que les besoins évoluent.

Address.hpp
1. namespace Bousk
2. {
3. namespace Network
4. {
5. class Address
6. {
7. public:
8. enum class Type {
9. None,
10. IPv4,
11. IPv6,
12. };
13. public:
14. Address() = default;
15. Address(const Address&);
16. Address(Address&&);
17. Address& operator=(const Address&);
18. Address& operator=(Address&&);
19. ~Address() = default;
20.
21. Address(const std::string& ip, uint16 port);
22. Address(const sockaddr_storage& addr);
23.
24. Type type() const { return mType; }
25. std::string address() const;
26. uint16 port() const { return mPort; }
27. std::string toString() const;
28.
29.
30. private:
31. sockaddr_storage mStorage{ 0 };
32. uint16 mPort{ 0 };
33. Type mType{ Type::None };
34. };
35. }
36. }

II-A-1 - Implémentation

L’implémentation de la classe Address sera principalement une révision des fonctions de manipulation d’adresses IP
et des structures sockaddr (_in, _out & _storage) utilisées jusqu’ici.

Ce devrait également être une des, si ce n’est la dernière, fois que vous manipulerez ces objets directement hérités
du C, laissant place à l’utilisation de votre nouvelle classe Address pour la suite.

-4-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Un champ mPort est ajouté et contiendra le port de l’adresse dans l’endianness locale.

Ceci est une optimisation afin de ne pas avoir à vérifier le type d’adresse avant de convertir
mStorage vers le bon type et convertir le port vers l’endianness locale – puisque dans
mStorage le port est stocké en endianness réseau – chaque fois que l’utilisateur veut cette
simple information.

De cette façon, l’utilisateur peut récupérer le port quand bon lui semble et autant de fois qu’il
le souhaite pour un cout nul ou presque.

Vous trouverez les implémentations principales ci-après. Peu de détails seront fournis puisque celles-ci devraient
être triviales à faire en lisant les chapitres précédents.

II-A-1-a - Constructeur depuis une chaîne et un port

Un constructeur permet de créer une adresse depuis une chaîne de caractères représentant une IP et un port.

Afin de vérifier que l’IP est valide, nous utiliserons la fonction inet_pton introduite dès le premier chapitre.

Pour rappel, inet_pton va interpréter la chaîne de caractères en paramètres en adresse IPv4 ou IPv6, si elle est valide.

Address.cpp
1. Address::Address(const std::string& ip, uint16_t port) noexcept
2. : mPort(port)
3. {
4. memset(&mStorage, 0, sizeof(mStorage));
5. if (!ip.empty())
6. {
7. // IpV4 ?
8. {
9. sockaddr_in& addrin = reinterpret_cast<sockaddr_in&>(mStorage);
10. in_addr& inaddr = addrin.sin_addr;
11. if (inet_pton(AF_INET, ip.c_str(), &inaddr) == 1)
12. {
13. mType = Type::IPv4;
14. addrin.sin_family = AF_INET;
15. Serialization::Conversion::ToNetwork(mPort, addrin.sin_port);
16. return;
17. }
18. }
19. // IpV6 ?
20. {
21. sockaddr_in6& addrin = reinterpret_cast<sockaddr_in6&>(mStorage);
22. in_addr6& inaddr = addrin.sin6_addr;
23. if (inet_pton(AF_INET6, ip.c_str(), &inaddr) == 1)
24. {
25. mType = Type::IPv6;
26. addrin.sin6_family = AF_INET6;
27. Serialization::Conversion::ToNetwork(mPort, addrin.sin6_port);
28. return;
29. }
30. }
31. }
32. }

II-A-1-b - Constructeur depuis un sockaddr_storage

Le deuxième constructeur intéressant permet de créer une adresse depuis un sockaddr_storage, qui est la structure
de stockage d’une adresse indépendamment du protocole.

-5-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Il s’agit de la structure que nous manipulons principalement puisqu’elle permet de gérer une adresse IPv4 ou IPv6
dans le même objet.

Address.cpp
1. Address::Address(const sockaddr_storage& addr) noexcept
2. {
3. set(addr);
4. }
5. void Address::set(const sockaddr_storage& src)
6. {
7. memcpy(&mStorage, &src, sizeof(mStorage));
8. if (mStorage.ss_family == AF_INET)
9. {
10. mType = Type::IPv4;
11. const sockaddr_in& addrin = reinterpret_cast<const sockaddr_in&>(mStorage);
12. Serialization::Conversion::ToLocal(addrin.sin_port, mPort);
13. }
14. else if (mStorage.ss_family == AF_INET6)
15. {
16. mType = Type::IPv6;
17. const sockaddr_in6& addrin = reinterpret_cast<const sockaddr_in6&>(mStorage);
18. Serialization::Conversion::ToLocal(addrin.sin6_port, mPort);
19. }
20. else
21. mType = Type::None;
22. }

II-A-1-c - Récupérer l’adresse IP sous format lisible

En utilisant inet_ntop nous pouvons proposer à l’utilisateur de récupérer l’adresse IP sous un format chaîne de
caractères lisible. Ce qui est toujours pratique pour des fichiers de logs par exemple.

Address.cpp
1. std::string Address::address() const
2. {
3. if (mType == Type::None)
4. return "<None>";
5. static constexpr int MaxBufferSize = std::max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN);
6. char buffer[MaxBufferSize];
7. // Utilisation de const_cast à cause de certaines API Windows... la variable n’est pas
modifiée donc c’est OK
8. if (mStorage.ss_family == AF_INET &&
inet_ntop(mStorage.ss_family, &reinterpret_cast<sockaddr_in*>(const_cast<sockaddr_storage*>(&mStorage))-
>sin_addr, buffer, MaxBufferSize) != nullptr)
9. return buffer;
10. if (mStorage.ss_family == AF_INET6 &&
inet_ntop(mStorage.ss_family, &reinterpret_cast<sockaddr_in6*>(const_cast<sockaddr_storage*>(&mStorage))-
>sin6_addr, buffer, MaxBufferSize) != nullptr)
11. return buffer;
12. return "<Unknown>";
13. }

II-A-1-d - Opérateurs de comparaison

Pouvoir identifier deux adresses comme identiques peut s’avérer utile.

Address.cpp
1. bool Address::operator==(const Address& other) const
2. {
3. if (mType != other.mType)
4. return false;
5. if (mType == Type::None)
6. return true;
7. if (mPort != other.mPort)
8. return false;

-6-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Address.cpp
9. if (mType == Type::IPv4)
10. {
11. return memcmp(&mStorage, &(other.mStorage), sizeof(mStorage)) == 0;
12. }
13. // IpV6
14. return memcmp(&reinterpret_cast<const
sockaddr_in6&>(mStorage).sin6_addr, &reinterpret_cast<const
sockaddr_in6&>(other.mStorage).sin6_addr, sizeof(IN6_ADDR)) == 0;
15. }

Après l’égalité, nous pouvons également fournir l’opérateur opposé : bool operator!=(const Address& other) const
{ return !(*this == other); }

II-A-1-e - Astuces et raccourcis : créer une adresse ANY

Pour utiliser un socket UDP, nous avions vu qu’il fallait utiliser bind en utilisant INADDR_ANY afin d’écouter sur
toutes les interfaces réseau. La même opération avait été faite pour créer un socket serveur TCP.

Afin de ne plus avoir à utiliser sockaddr_in à la faveur de Address, il est intéressant de proposer une interface
permettant de créer un tel objet, sans avoir à manipuler quelconque objet de l’API socket : static Address Any(Type
type, uint16 port);

Address.cpp
1. Address Address::Any(Type type, uint16 port)
2. {
3. switch (type)
4. {
5. case Type::IPv4:
6. {
7. sockaddr_storage storage{ 0 };
8. sockaddr_in& addr = reinterpret_cast<sockaddr_in&>(storage);
9. addr.sin_addr.s_addr = INADDR_ANY;
10. addr.sin_port = htons(port);
11. addr.sin_family = AF_INET;
12. return Address(storage);
13. }
14. case Type::IPv6:
15. {
16. sockaddr_storage storage{ 0 };
17. sockaddr_in6& addr = reinterpret_cast<sockaddr_in6&>(storage);
18. addr.sin6_addr = in6addr_any;
19. addr.sin6_port = htons(port);
20. addr.sin6_family = AF_INET6;
21. return Address(reinterpret_cast<sockaddr_storage&>(addr));
22. }
23. default:
24. assert(false);
25. return Address();
26. }
27. }

II-A-1-f - Aller plus loin

Vous aurez peut-être remarqué qu’il n’existe pas d’interface pour récupérer le sockaddr_storage interne à Address.
En l’état notre nouvelle classe est donc inutilisable…

Seulement, il est dommage à mon avis de proposer une telle classe pour finalement laisser l’utilisateur avoir à
manipuler une structure héritée du C pour réaliser ses appels bas niveau.

-7-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

C’est pourquoi, je propose de faire de Address l’unique point d’entrée en termes de manipulation de structure
d’adresse : toutes les manipulations de structure d’adresse C sont uniquement internes à Address, et c’est
uniquement un objet Address qui sera manipulé par l’utilisateur.

Voilà les fonctions dont on parle, qui sont celles que nous avons rencontrées dans le cours jusque-là, tant TCP
qu’UDP, qui nécessitent une manipulation de sockaddr_in ou sockaddr_storage.

Address.hpp
namespace Bousk
{
namespace Network
{
class Address
{

public:
// Connecte le socket en paramètre à l’adresse interne
// Retourne true si la connexion réussit ou débute (socket non bloquant), false sinon
bool connect(SOCKET sckt) const;
// Accepte une connexion entrante sur le socket en paramètre, puis met à jour l’adresse
interne avec celle de l’émetteur
// Retourne true si un nouveau socket a été accepté et met à jour newClient avec le nouveau
socket, false sinon
bool accept(SOCKET sckt, SOCKET& newClient);
// Assigne l’adresse interne au socket en paramètre
bool bind(SOCKET sckt) const;
// Envoie des données depuis le socket en paramètre vers l’adresse interne
int sendTo(SOCKET sckt, const char* data, size_t datalen) const;
// Reçoit des données depuis le socket en paramètre puis met à jour l’adresse interne avec
celle de l’émetteur
int recvFrom(SOCKET sckt, uint8* buffer, size_t bufferSize);
};
}
}

Quant aux implémentations, là aussi elles devraient être automatiques puisque déjà vues :

Address.cpp
bool Address::connect(SOCKET sckt) const
{
return ::connect(sckt, reinterpret_cast<const sockaddr*>(&mStorage), sizeof(mStorage)) == 0;
}
bool Address::accept(SOCKET sckt, SOCKET& newClient)
{
sockaddr_storage storage{ 0 };
sockaddr_in& addr = reinterpret_cast<sockaddr_in&>(storage);
socklen_t addrlen = sizeof(addr);
SOCKET newClientSocket = ::accept(sckt, reinterpret_cast<sockaddr*>(&addr), &addrlen);
if (newClientSocket == INVALID_SOCKET)
{
return false;
}
set(storage);
newClient = newClientSocket;
return true;
}
bool Address::bind(SOCKET sckt) const
{
return ::bind(sckt, reinterpret_cast<const sockaddr*>(&mStorage), sizeof(mStorage)) == 0;
}
int Address::sendTo(SOCKET sckt, const char* data, size_t datalen) const
{
return sendto(sckt, data, static_cast<int>(datalen), 0, reinterpret_cast<const
sockaddr*>(&mStorage), sizeof(mStorage));
}
int Address::recvFrom(SOCKET sckt, uint8* buffer, size_t bufferSize)
{
sockaddr_storage storage{ 0 };

-8-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Address.cpp
sockaddr_in& from = reinterpret_cast<sockaddr_in&>(storage);
socklen_t fromlen = sizeof(from);
int ret =
recvfrom(sckt, reinterpret_cast<char*>(buffer), static_cast<int>(bufferSize), 0, reinterpret_cast<sockaddr*>(&fro
if (ret >= 0)
{
set(storage);
}
return ret;
}

Avec ces implémentations supplémentaires, l’initialisation d’un socket UDP passe de

sockaddr_in addr;
addr.sin_addr.s_addr = INADDR_ANY; // permet d'écouter sur toutes les interfaces locales
addr.sin_port = htons(port); // toujours penser à traduire le port en endianess réseau
addr.sin_family = AF_INET; // notre adresse est IPv4
int res = bind(sckt, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
if (res != 0)
// erreur

const Address addr = Address::Any(Address::Type::IPv4, port);


if (!addr.bind(mSocket))
// erreur

Ce qui est bien plus lisible et plutôt élégant !

II-B - Interface de connexion

Maintenant que nous avons une adresse aisément manipulable (tant par le moteur que par les utilisateurs de celui-
ci), nous pouvons ajouter une interface à notre client UDP pour initialiser une connexion.

UDPClient.hpp
namespace Bousk
{
namespace Network
{
namespace Messages
{
class Base;
}
namespace UDP
{
class DistantClient;
class Client
{
friend class DistantClient;
public:

bool connect(const Address& addr);
};
}
}
}

II-C - Demande de connexion

La connexion démarre par une demande de connexion, que la machine distante peut accepter ou refuser.

-9-
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Les premiers échanges de données symbolisent le début de la connexion. Nous n’utilisons pas de paquet ou données
spécifiques : dès lors qu’une machine reçoit des données d’une autre, celle-ci est considérée comme souhaitant se
connecter.

La machine émettrice de la demande de connexion est donc en demande de connexion à la machine distante de
facto. La machine distante doit ensuite accepter cette connexion pour que la relation soit bilatérale ou la refuser.

II-C-1 - Refuser une connexion

Pour refuser une connexion, il suffit d’ignorer la demande de connexion. Celle-ci finira par expirer via un timeout qui
sera ajouté et défini dans les paragraphes suivants.

II-C-2 - Accepter une connexion

Pour accepter une connexion, il faut se connecter au demandeur via la même fonction connect.

II-D - Message de connexion entrante

À l’instar de la connexion, déconnexion ou réception de données, il faut ajouter un message de demande de connexion
pour notifier l’application qu’un client distant souhaite se connecter à notre socket :

class Base
{

enum class Type {
IncomingConnection,
Connection,
Disconnection,
UserData,
};

};
class IncomingConnection : public Base
{
DECLARE_MESSAGE(IncomingConnection);
public:
IncomingConnection(const Address& emitter, uint64 emitterid)
: Base(Type::IncomingConnection, emitter, emitterid)
{}
};

II-E - Mise en attente des données

Une machine faisant une demande de connexion peut commencer à envoyer des données.

Dans le cas de données non fiables, il est correct de les ignorer, mais pas s’il s’agit de données fiables. Dans le
cas de la machine qui reçoit la connexion, elle ne doit pas avoir à traiter des données alors qu’elle n’a pas encore
accepté la connexion – et va potentiellement la refuser.

Il faut donc avoir une file d’attente des messages fiables reçus avant d’avoir accepté la connexion entrante.

II-E-1 - Interface des protocoles

Ajoutons un accesseur afin de savoir si notre protocole est fiable ou non :

- 10 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

ProtocolInterface.hpp
class IProtocol
{

virtual bool isReliable() const = 0;
};

II-E-2 - Gestionnaire de protocoles

Ensuite, nous agissons lors du processus d’extraction des messages afin d’ignorer ceux issus d’un protocole non
fiable si la connexion n’a pas été acceptée :

ChannelsHandler.hpp
class ChannelsHandler
{

std::vector<std::vector<uint8_t>> process(bool isConnected);
};

ChannelsHandler.cpp
std::vector<std::vector<uint8_t>> ChannelsHandler::process(bool isConnected)
{
std::vector<std::vector<uint8_t>> messages;
for (auto& channel : mChannels)
{
std::vector<std::vector<uint8_t>> protocolMessages = channel->process();
if (!protocolMessages.empty() && (channel->isReliable() || isConnected))
{

}
}
return messages;
}

II-E-3 - Client distant

Enfin une dernière modification dans le client distant, utilisateur du gestionnaire de protocoles, afin de passer le
nouveau paramètre :

DistantClient.cpp
void DistantClient::onDataReceived(const uint8_t* data, const size_t datasize)
{

auto receivedMessages = mChannelsHandler.process(isConnected());

}

Voilà qui gère le fait de ne pas traiter de messages non fiables sans être connectés, mais on ne couvre pas la
deuxième partie du problème : la réception de messages (fiables) avant d’avoir accepté la connexion.

Pour cela, il faut enregistrer nos messages fiables reçus avant que la connexion ne soit établie dans une liste
secondaire :

DistantClient.hpp
class DistantClient
{

std::vector<std::unique_ptr<Messages::Base>> mPendingMessages; // Stocke les messages avant que
la connexion ne soit acceptée
};

- 11 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Le moyen le plus simple d’y parvenir est de modifier le point d’entrée de tous les messages : onMessageReady :

DistantClient.cpp
void DistantClient::onMessageReady(std::unique_ptr<Messages::Base>&& msg)
{
if (isConnected())
{
mClient.onMessageReady(std::move(msg));
}
else if (isConnecting())
{
mPendingMessages.push_back(std::move(msg));
}
}

Puis, quand la connexion est réalisée, transférer ces messages dans la liste d’attente de messages reçus et
disponibles pour l’application :

DistantClient.cpp
void DistantClient::onConnected()
{
mState = State::Connected;
maintainConnection();
onMessageReady(std::make_unique<Messages::Connection>(mAddress,
mClientId, Messages::Connection::Result::Success));
// Transfert des messages en attente !
for (auto&& pendingMessage : mPendingMessages)
{
onMessageReady(std::move(pendingMessage));
}
}

Enfin, il faut s’assurer que le message de demande de connexion ne soit pas filtré et arrive au client pour pouvoir
l’accepter :

DistantClient.cpp
void DistantClient::onConnectionReceived()
{
if (mState == State::None)
{
mState = State::ConnectionReceived;
maintainConnection();
// Transférer le message de demande de connexion à l’application
mClient.onMessageReady(std::make_unique<Messages::IncomingConnection>(mAddress, mClientId));
}

}

III - Déconnexion

Tout comme pour la connexion, nous ajoutons une interface pour déconnecter une adresse donnée :

UDPClient.hpp
namespace Bousk
{
namespace Network
{
namespace Messages
{
class Base;
}
namespace UDP
{
class DistantClient;

- 12 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

UDPClient.hpp
class Client
{
friend class DistantClient;
public:

void disconnect(const Address& addr);
};
}
}
}

III-A - Comment se déconnecter ?

La déconnexion est l’inverse de la connexion : l’arrêt d’échange de données.

Pour déconnecter une machine, il suffit donc de cesser l’envoi de datagrammes vers celle-ci.

Dans les faits, ce n’est pas si simple…

III-B - Processus de déconnexion

Puisque les machines s’échangent des données en continu, la déconnexion doit être entendue de chacune d’elles
afin de s’effectuer.

Imaginez le scénario suivant :

• A et B sont connectés ;
• B veut se déconnecter de A et cesse tout envoi vers A
• B supprime A de sa liste de clients ;
• A continue ses envois vers B ;
• B reçoit des données de A qu’il ne connait plus
• B traite ces données comme une nouvelle demande de connexion.

Dans ce cas, il est impossible pour A et B de se déconnecter – et les datagrammes reçus en double ou en retard
n’ont même pas été pris en compte !

Il faut donc que la déconnexion soit un processus dans le temps pour que chaque machine le détecte.

Une déconnexion se déroulera en deux étapes :

• l’arrêt de l’envoi de données pour passer dans un état « Déconnexion en cours » ;


• puis le passage à un état « Déconnecté » après un certain délai dans l’état « Déconnexion en cours » ;
• enfin la suppression du client.

Une fois le processus de déconnexion amorcé, il n’est pas possible de revenir en arrière.

La déconnexion doit se réaliser puis une nouvelle connexion doit être émise et établie pour
reprendre l’échange de données.

III-C - Timeout

Comme indiqué pour refuser une connexion, une notion de timeout doit apparaître.

- 13 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Le timeout aura pour rôle principal de détecter l’arrêt de réception de données et déclencher le processus de
déconnexion puisque la machine distante ne souhaite plus communiquer – ou ne répond plus.

Le timeout aura donc deux conséquences :

• si aucune donnée n’a été reçue depuis un timeout, alors la connexion est perdue et l’état passe à
« Déconnexion en cours » ;
• après deux timeout, on passe dans l’état final « Déconnecté ».

Le timeout est généralement de une seconde. Si vous souhaitez pouvoir supporter des clients avec une latence
plus élevée, vous devrez le montrer.

Le processus de timeout prend place dans le processus d’envoi, puisque c’est le code qui sera exécuté de façon
régulière.

DistantClient.hpp
class DistantClient
{

std::chrono::milliseconds mLastKeepAlive; // Dernière fois que la connexion a été marquée
valide, pour gérer le timeout
};

Cette variable doit être initialisée avec un appel à Utils::Now(); dans le constructeur.

DistantClient.cpp
void DistantClient::maintainConnection()
{
mLastKeepAlive = Utils::Now();
}
void DistantClient::processSend(const size_t maxDatagrams /*= 0*/)
{
const auto now = Utils::Now();

if (isDisconnecting() && now > mLastKeepAlive + 2 * GetTimeout())
{
// Après 2 timeouts, la connexion est marquée comme perdue
// Ceci laisse assez de temps pour que chaque partie remarque la déconnexion et agisse en
conséquence
mState = State::Disconnected;
}
else if (isConnected() && now > mLastKeepAlive + GetTimeout())
{
onConnectionLost();
}
}

III-D - Datagramme de déconnexion

Nous pouvons utiliser ce délai pour envoyer des informations sur la déconnexion, en particulier pour la faire apparaître
comme normale et non comme perte de connexion par la machine distante.

Pour cela, ajoutons un type de datagramme différent, en plus de l’unique type que nous possédons actuellement
pour envoyer des données utilisateur.

Rajoutons donc un champ de type dans l’en-tête du datagramme :

Datagram.hpp
struct Datagram
{

- 14 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Datagram.hpp
enum class Type : uint8 {
ConnectedData,
Disconnection,
};
struct Header
{

Type type;
};
};

En limitant le type à un uint8, nous simplifions sa sérialisation tout en permettant d’en ajouter d’autres par la suite
si besoin.

Ce datagramme sera envoyé pendant le processus de déconnexion :

DistantClient.cpp
void DistantClient::processSend(const size_t maxDatagrams /*= 0*/)
{
const auto now = Utils::Now();

if (isDisconnecting()))
{
if (now > mLastKeepAlive + 2 * GetTimeout())
{
// Après 2 timeouts, la connexion est marquée comme perdue
// Ceci laisse assez de temps pour que chaque partie remarque la déconnexion et agisse en
conséquence
mState = State::Disconnected;
}
else
{
// Envoyer un datagramme de déconnexion pendant le processus de déconnexion pour informer la
machine distante
Datagram datagram;
fillDatagramHeader(datagram, Datagram::Type::Disconnection);
send(datagram);
}
}
else if (isConnected() && now > mLastKeepAlive + GetTimeout())
{
onConnectionLost();
}
}

III-D-1 - Gestion du datagramme de déconnexion

Puisque nous avons un nouveau type de datagramme, nous devons le gérer à sa réception :

DistantClient.cpp
void DistantClient::onDatagramReceived(Datagram&& datagram)
{

switch (datagram.header.type)
{
case Datagram::Type::ConnectedData:
{
onDataReceived(datagram.data.data(), datagram.datasize);
} break;
case Datagram::Type::Disconnection:
{
onDisconnectionFromOtherEnd();
}
}
}

- 15 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Avec

DistantClient.cpp
void DistantClient::onDisconnectionFromOtherEnd()
{
if (isConnecting())
{
onConnectionRefused();
}
else if (isConnected())
{
mState = State::Disconnecting;
}
}

III-E - Reconnexion

La reconnexion pourra se produire après que l’état « Déconnecté » a été mis en place et que le client a été oublié.

Du point de vue du moteur, il s’agit d’une nouvelle connexion qui n’a aucun lien avec une connexion précédente.

III-F - Quand notifier la déconnexion

Puisque la déconnexion est un processus non instantané, il s’agit de rendre ça au plus simple d’utilisation pour
l’utilisateur.

L’exigence sera donc que l’utilisateur puisse effectuer une nouvelle connexion, dès qu’il reçoit un message de
déconnexion.

Nous devons donc notifier via le message correspondant l’application quand le processus de déconnexion est
terminé et non quand il débute.

III-F-1 - Raison de la déconnexion

Le message sera donc délivré à l’application après que la déconnexion a été perçue, et plus exactement un timeout
plus tard d’après ce que nous avons établi plus haut.

Nous devons alors enregistrer la raison de la déconnexion, s’il y en a une, afin de pouvoir la récupérer le moment venu.

DistantClient.hpp
class DistantClient
{
enum class DisconnectionReason {
None,
Refused,
ConnectionTimedOut,
Disconnected,
Lost,
};

DisconnectionReason mDisconnectionReason{ DisconnectionReason::None };
};

Davantage de raisons pourront être ajoutées quand le besoin se présentera.

Cette raison doit bien sûr être initialisée à None puis mise à jour quand la déconnexion est démarrée. Sa valeur
dépendra du contexte quand cela se produit.

- 16 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Nous avons pour cela des fonctions pour chaque cas, certaines déjà introduites et d’autres qui le seront plus loin.

DistantClient.cpp
void DistantClient::onDisconnectionFromOtherEnd()
{
if (isConnecting())
{
onConnectionRefused();
}
else if (isConnected())
{
mState = State::Disconnecting;
mDisconnectionReason = DisconnectionReason::DisconnectedFromOtherEnd;
}
}
void DistantClient::onConnectionLost()
{
if (isConnected())
{
// Démarrer la déconnexion et sauvegarder sa raison pour pouvoir la notifier plus tard
mState = State::Disconnecting;
mDisconnectionReason = DisconnectionReason::Lost;
}
}
void DistantClient::onConnectionRefused()
{
if (mState == State::ConnectionSent)
{
mDisconnectionReason = DisconnectionReason::Refused;
}
mState = State::Disconnecting;
}
void DistantClient::onConnectionTimedOut()
{
if (mState == State::ConnectionSent)
{
mDisconnectionReason = DisconnectionReason::ConnectionTimedOut;
}
mState = State::Disconnecting;
}
void DistantClient::disconnect()
{
mDisconnectionReason = DisconnectionReason::Disconnected;
mState = State::Disconnecting;
}

Il est important de ne pas écraser cette raison une fois déterminée afin de ne pas fausser la déconnexion et les
informations envoyées au programme.

III-F-2 - Déconnexion ne devant pas être notifiée

Il existe un cas où la déconnexion ne doit pas être notifiée : s’il s’agit d’une connexion non acceptée que l’on a laissé
périmer.

Dans ce cas, inutile d’importuner l’utilisateur d’une déconnexion alors qu’il n’a jamais accepté cette connexion au
préalable.

La machine distante doit par contre recevoir un message de connexion échouée.

DistantClient.cpp
void DistantClient::processSend(const size_t maxDatagrams /*= 0*/)
{
const auto now = Utils::Now();

if (isDisconnecting())

- 17 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

DistantClient.cpp
{
if (now > mLastKeepAlive + 2 * GetTimeout())
{
mState = State::Disconnected;
// Informer de la déconnexion, si besoin
switch (mDisconnectionReason)
{
case DisconnectionReason::Disconnected:
case DisconnectionReason::DisconnectedFromOtherEnd:
mClient.onMessageReady(std::make_unique<Messages::Disconnection>(mAddress,
mClientId, Messages::Disconnection::Reason::Disconnected));
break;
case DisconnectionReason::Lost:
mClient.onMessageReady(std::make_unique<Messages::Disconnection>(mAddress,
mClientId, Messages::Disconnection::Reason::Lost));
break;
case DisconnectionReason::Refused:
mClient.onMessageReady(std::make_unique<Messages::Connection>(mAddress,
mClientId, Messages::Connection::Result::Refused));
break;
case DisconnectionReason::ConnectionTimedOut:
mClient.onMessageReady(std::make_unique<Messages::Connection>(mAddress,
mClientId, Messages::Connection::Result::TimedOut));
break;
}
}
else if (mDisconnectionReason != DisconnectionReason::None && mDisconnectionReason !
= DisconnectionReason::Lost)
{
// Envoi du datagramme de déconnexion s’il s’agit d’une fin de connexion normale
Datagram datagram;
fillDatagramHeader(datagram, Datagram::Type::Disconnection);
send(datagram);
}
}
else if (isConnected() && now > mLastKeepAlive + GetTimeout())
{
onConnectionLost();
}}

III-G - Échec de connexion

En plus de détecter les pertes de connexion, le timeout précédemment introduit pourra aussi détecter une connexion
qui échoue par un dépassement du temps imparti.

Pour cela, nous devons utiliser un second compteur dédié :

DistantClient.hpp
Class DistantClient
{

std::chrono::milliseconds mConnectionStartTime; // Démarrage de la connexion, pour timeout de
connexion
};

Qui sera également initialisé avec Utils::Now(); et qui permettra de détecter un timeout pendant le processus de
connexion.

DistantClient.cpp
void DistantClient::processSend(const size_t maxDatagrams /*= 0*/)
{
const auto now = Utils::Now();

if (isDisconnecting())
{

- 18 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

DistantClient.cpp

}
else if (isConnected() && now > mLastKeepAlive + GetTimeout())
{
onConnectionLost();
}
else if (isConnecting() && now > mConnectionStartTime + GetTimeout())
{
// La connexion n’a pas été acceptée dans les temps
onConnectionTimedOut();
}
}

Avec

DistantClient.cpp
void DistantClient::onConnectionTimedOut()
{
if (mState == State::ConnectionSent)
{
mDisconnectionReason = DisconnectionReason::ConnectionTimedOut;
}
mState = State::Disconnecting;
}

IV - Maintenir une connexion

IV-A - État des lieux

Nous avons pour le moment en notre possession un protocole qui fait un suivi des datagrammes reçus et différents
canaux de communication. Ces canaux remplissent parfaitement leur rôle de transmission de données mises en file
d’envoi par l’utilisateur.

Mais un utilisateur peut vouloir garder sa connexion sans pour autant avoir des données probantes à envoyer. Exiger
de l’utilisateur de mettre en continu des données pour l’envoi, dans le seul but de maintenir sa connexion, serait
très contraignant.

IV-A-1 - Keepalive

Afin de remédier à ce problème, nous allons mettre en place un signal keepalive au sein du protocole.

Ce signal sera un datagramme particulier, contenant un minimum de données, qui sera transmis lorsque l’envoi de
données est effectué alors que les canaux n’ont rien à envoyer.

IV-B - Types de datagrammes

Nous devons donc ajouter un type de datagramme qui contiendra uniquement des données propres au signal
keepalive.

Datagram.hpp
struct Datagram
{

enum class Type : uint8 {
ConnectedData,
Disconnection,
KeepAlive,
};

- 19 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Datagram.hpp
struct Header
{

Type type;
};
};

IV-C - Mise à jour de l’envoi

Il faut ensuite mettre à jour l’envoi des données afin d’envoyer un keepalive si aucune donnée n’est disponible à
l’envoi :

DistantClient.cpp
void DistantClient::processSend(const size_t maxDatagrams /*= 0*/)
{
const auto now = Utils::Now();
// Pendant que la connexion est en attente d’approbation, nous devons continuer l’envoi de
données afin de la conserver disponible
if (isConnecting() || isConnected())
{
for (size_t loop = 0; maxDatagrams == 0 || loop < maxDatagrams; ++loop)
{
Datagram datagram;
datagram.datasize = mChannelsHandler.serialize(datagram.data.data(), Datagram::DataMaxSize,
mNextDatagramIdToSend);
if (datagram.datasize > 0)
{
fillDatagramHeader(datagram, Datagram::Type::ConnectedData);
send(datagram);
}
else
{
if (loop == 0)
{
// Rien à envoyer, envoyons un keepalive pour maintenir la connexion
fillKeepAlive(datagram);
send(datagram);
}
break;
}
}
}

}

IV-D - Keepalive connecté

La seule information contenue dans le keepalive sera si la connexion est souhaitée (émise ou acceptée) ou non.

Pour cela, nous utilisons notre système de sérialisation pour créer le contenu du datagramme keepalive qui sera
constitué d’un unique booléen :

DistantClient.cpp
void DistantClient::fillKeepAlive(Datagram& dgram)
{
fillDatagramHeader(dgram, Datagram::Type::KeepAlive);
// Notifier la machine distante si nous devons être connecté ou demandons la connexion
Serialization::Serializer serializer;
serializer.write(mState == State::ConnectionSent || isConnected());
memcpy(dgram.data.data(), serializer.buffer(), serializer.bufferSize());
dgram.datasize = serializer.bufferSize();
}

- 20 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Bien sûr il y aura un code similaire pour traiter un datagramme keepalive et extraire cette donnée :

DistantClient.cpp
void DistantClient::onDatagramReceived(Datagram&& datagram)
{

switch (datagram.header.type)
{

case Datagram::Type::KeepAlive:
{
handleKeepAlive(datagram.data.data(), datagram.datasize);
} break;
}
}

void DistantClient::handleKeepAlive(const uint8* data, const size_t datasize)


{
maintainConnection();
if (mState == State::None || isConnecting())
{
Serialization::Deserializer deserializer(data, datasize);
bool isConnectedKeepAlive = false;
if (deserializer.read(isConnectedKeepAlive) && isConnectedKeepAlive)
{
onConnectionReceived();
}
}
}

V - Diagramme des échanges de données

Voici un diagramme récapitulatif des échanges de données tout au long d’une connexion.

- 21 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Vous pouvez y voir les différents états du DistantClient et les messages reçus par l’application.

Chaque datagramme, excepté ceux de type Disconnection, peut contenir des données utilisateur s’il y en a
disponibles et éligibles à l’envoi (type ConnectedData), ou un keepalive (type KeepAlive) connecté ou non.

- 22 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Souvenez-vous que chaque datagramme peut être perdu, ce qui est symbolisé par les flèches hachurées. Pour
indiquer un datagramme reçu, qui déclenche une action, il s’agit d’une flèche entière.

VI - Tests

Cette fois les tests ne seront pas assurés par un test unitaire, mais par des programmes de test afin de vérifier que
la connexion s’effectue et qu’un timeout finit par arriver.

Chaque programme de test créera deux clients UDP dans deux threads, ouvrant chacun un port et devant se
connecter l’un à l’autre.

VI-A - Test de connexion

Le premier test sert à illustrer la mise en place de la connexion et l’échange de données fiables et ordonnées entre
deux machines. Il s’agit d’envoyer une liste de chaînes de caractères qui devra être identique (chaque chaîne et leur
ordre) chez chaque client :

Samples/UDP/Connection/Main.cpp
1. #include "Sockets.hpp"
2. #include "UDP/UDPClient.hpp"
3. #include "UDP/Protocols/ReliableOrdered.hpp"
4. #include "Serialization/Deserializer.hpp"
5. #include "Serialization/Serializer.hpp"
6. #include "Messages.hpp"
7. #include "Errors.hpp"
8.
9. #include <iostream>
10. #include <mutex>
11. #include <string>
12. #include <thread>
13.
14. int main()
15. {
16. if (!Bousk::Network::Start())
17. {
18. std::cout << "Erreur d’initialisation : " << Bousk::Network::Errors::Get();
19. return -1;
20. }
21.
22. const Bousk::Network::Address
client1 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 8888);
23. const Bousk::Network::Address
client2 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 9999);
24.
25. const std::vector<std::string>
messagesToSend = {"mon", "premier", "transfert", "udp", "ordonne", "fiable"};
26. std::mutex coutMutex;
27. // Créer un thread par client
28. std::thread t1([&]()
29. {
30. Bousk::Network::UDP::Client client;
31. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
32. if (!client.init(client1.port()))
33. {
34. std::scoped_lock lock(coutMutex);
35. std::cout << "Client 1 erreur d’initialisation: " << Bousk::Network::Errors::Get();
36. return;
37. }
38. {
39. std::scoped_lock lock(coutMutex);
40. std::cout << "Client 1 initialisé sur le port " << client1.port() << std::endl;
41. }
42. // Connecter client 1 à client 2
43. client.connect(client2);
44. {

- 23 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/Connection/Main.cpp
45. std::scoped_lock lock(coutMutex);
46. std::cout << "Client 1 connexion à " << client2.toString() << " en cours..." << std::endl;
47. }
48. std::vector<std::string> receivedMessages;
49. for (bool exit = false; !exit;)
50. {
51. client.receive();
52. auto messages = client.poll();
53. for (auto&& message : messages)
54. {
55. if (message->is<Bousk::Network::Messages::Connection>())
56. {
57. if (message->emitter() != client2)
58. {
59. std::scoped_lock lock(coutMutex);
60. std::cout << "Connexion inattendue de " << message->emitter().toString() << " (devrait
être de " << client2.toString() << ")" << std::endl;
61. continue;
62. }
63. else
64. {
65. std::scoped_lock lock(coutMutex);
66. std::cout << "Client 2 [" << client2.toString() << "] connecté à client
1" << std::endl;
67. }
68. }
69. else if (message->is<Bousk::Network::Messages::UserData>())
70. {
71. const Bousk::Network::Messages::UserData* userdata = message-
>as<Bousk::Network::Messages::UserData>();
72. Bousk::Serialization::Deserializer deserializer(userdata->data.data(), userdata-
>data.size());
73. std::string msg;
74. if (!deserializer.read(msg))
75. {
76. std::cout << "Erreur de désérialisation du message !" << std::endl;
77. return;
78. }
79. receivedMessages.push_back(msg);
80. if (receivedMessages == messagesToSend)
81. {
82. std::scoped_lock lock(coutMutex);
83. std::cout << "Tous les messages reçus en ordre !" << std::endl;
84. std::cout << "Déconnexion du client 2..." << std::endl;
85. exit = true;
86. }
87. }
88. else if (message->is<Bousk::Network::Messages::Disconnection>())
89. {
90. assert(message->emitter() == client2);
91. std::cout << "Extinction de client 1..." << std::endl;
92. exit = true;
93. }
94. }
95. client.processSend();
96. std::this_thread::sleep_for(std::chrono::microseconds(1));
97. }
98. client.release();
99. });
100. std::thread t2([&]()
101. {
102. Bousk::Network::UDP::Client client;
103. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
104. if (!client.init(client2.port()))
105. {
106. std::scoped_lock lock(coutMutex);
107. std::cout << "Client 2 erreur d’initialisation : " << Bousk::Network::Errors::Get();
108. return;
109. }
110. {

- 24 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/Connection/Main.cpp
111. std::scoped_lock lock(coutMutex);
112. std::cout << "Client 2 initialisé sur le port " << client2.port() << std::endl;
113. }
114. for (bool connected = false, exit = false; !exit;)
115. {
116. client.receive();
117. auto messages = client.poll();
118. for (auto&& message : messages)
119. {
120. if (message->is<Bousk::Network::Messages::IncomingConnection>())
121. {
122. if (message->emitter() != client1)
123. {
124. std::scoped_lock lock(coutMutex);
125. std::cout << "Connexion entrante inattendue de " << message-
>emitter().toString() << " (devrait être " << client1.toString() << ")" << std::endl;
126. client.disconnect(message->emitter());
127. continue;
128. }
129. else
130. {
131. std::scoped_lock lock(coutMutex);
132. std::cout << "Client 2 reçoit une demande de connexion de [" << message-
>emitter().toString() << "] (client 1)... et l’accepte" << std::endl;
133. }
134. client.connect(message->emitter());
135. }
136. else if (message->is<Bousk::Network::Messages::Connection>())
137. {
138. if (message->emitter() != client1)
139. {
140. std::scoped_lock lock(coutMutex);
141. std::cout << "Connexion inattendue de " << message->emitter().toString() << " (devrait
être " << client1.toString() << ")" << std::endl;
142. continue;
143. }
144. else
145. {
146. std::scoped_lock lock(coutMutex);
147. std::cout << "Client 1 [" << client1.toString() << "] connecté à client
2" << std::endl;
148. }
149. // Envoi des chaînes de caractères à client 1, 1 chaîne par message
150. for (const auto& msg : messagesToSend)
151. {
152. Bousk::Serialization::Serializer serializer;
153. if (!serializer.write(msg))
154. {
155. std::scoped_lock lock(coutMutex);
156. std::cout << "Erreur de sérialisation de la chaîne \"" << msg << "\" !" << std::endl;
157. return;
158. }
159. std::vector<Bousk::uint8> buffer(serializer.buffer(), serializer.buffer() +
serializer.bufferSize());
160. client.sendTo(client1, std::move(buffer), 0);
161. }
162. connected = true;
163. }
164. else if (connected)
165. {
166. // Attente que client 1 se déconnecte
167. if (message->is<Bousk::Network::Messages::Disconnection>())
168. {
169. std::scoped_lock lock(coutMutex);
170. assert(message->emitter() == client1);
171. std::cout << "Déconnexion de client 1... [" << message-
>as<Bousk::Network::Messages::Disconnection>()->reason << "]" << std::endl;
172. exit = true;
173. }
174. }

- 25 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/Connection/Main.cpp
175. }
176. client.processSend();
177. std::this_thread::sleep_for(std::chrono::microseconds(1));
178. }
179. std::cout << "Fin de processus normal." << std::endl;
180. client.release();
181. });
182.
183. t1.join();
184. t2.join();
185.
186. Bousk::Network::Release();
187. return 0;
188. }

VI-B - Test du timeout

Le second test reprend le squelette du test précédent, mais une fois la connexion établie éteint un client afin que
l’autre détecte une perte de connexion :

Samples/UDP/Timeout/Main.cpp
1. #include "Sockets.hpp"
2. #include "UDP/UDPClient.hpp"
3. #include "UDP/Protocols/ReliableOrdered.hpp"
4. #include "Serialization/Deserializer.hpp"
5. #include "Serialization/Serializer.hpp"
6. #include "Messages.hpp"
7. #include "Errors.hpp"
8.
9. #include <iostream>
10. #include <mutex>
11. #include <string>
12. #include <thread>
13.
14. int main()
15. {
16. if (!Bousk::Network::Start())
17. {
18. std::cout << " Erreur d’initialisation : " << Bousk::Network::Errors::Get();
19. return -1;
20. }
21.
22. const Bousk::Network::Address
client1 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 8888);
23. const Bousk::Network::Address
client2 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 9999);
24.
25. std::mutex coutMutex;
26. std::thread t1([&]()
27. {
28. Bousk::Network::UDP::Client client;
29. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
30. if (!client.init(client1.port()))
31. {
32. std::scoped_lock lock(coutMutex);
33. std::cout << "Client 1 erreur d’initialisation: " << Bousk::Network::Errors::Get();
34. return;
35. }
36. {
37. std::scoped_lock lock(coutMutex);
38. std::cout << "Client 1 initialisé sur le port " << client1.port() << std::endl;
39. }
40. client.connect(client2);
41. {
42. std::scoped_lock lock(coutMutex);
43. std::cout << "Client 1 connexion à " << client2.toString() << " en cours..." << std::endl;
44. }

- 26 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/Timeout/Main.cpp
45. std::vector<std::string> receivedMessages;
46. for (bool exit = false; !exit;)
47. {
48. client.receive();
49. auto messages = client.poll();
50. for (auto&& message : messages)
51. {
52. if (message->is<Bousk::Network::Messages::Connection>())
53. {
54. if (message->emitter() != client2)
55. {
56. std::scoped_lock lock(coutMutex);
57. std::cout << "Connexion inattendue de " << message->emitter().toString() << " (devrait
être de " << client2.toString() << ")" << std::endl;
58. continue;
59. }
60. else
61. {
62. std::scoped_lock lock(coutMutex);
63. std::cout << "Client 2 [" << client2.toString() << "] connecté à client
1" << std::endl;
64. std::cout << "Extinction de client 1..." << std::endl;
65. exit = true;
66. }
67. }
68. }
69. client.processSend();
70. std::this_thread::sleep_for(std::chrono::microseconds(1));
71. }
72. client.release();
73. });
74. std::thread t2([&]()
75. {
76. Bousk::Network::UDP::Client client;
77. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
78. if (!client.init(client2.port()))
79. {
80. std::scoped_lock lock(coutMutex);
81. std::cout << "Client 2 erreur d’initialisation : " << Bousk::Network::Errors::Get();
82. return;
83. }
84. {
85. std::scoped_lock lock(coutMutex);
86. std::cout << "Client 2 initialisé sur le port " << client2.port() << std::endl;
87. }
88. for (bool exit = false; !exit;)
89. {
90. client.receive();
91. auto messages = client.poll();
92. for (auto&& message : messages)
93. {
94. if (message->is<Bousk::Network::Messages::IncomingConnection>())
95. {
96. if (message->emitter() != client1)
97. {
98. std::scoped_lock lock(coutMutex);
99. std::cout << "Connexion entrante inattendue de " << message-
>emitter().toString() << " (devrait être de " << client1.toString() << ")" << std::endl;
100. client.disconnect(message->emitter());
101. continue;
102. }
103. else
104. {
105. std::scoped_lock lock(coutMutex);
106. std::cout << "Client 2 reçoit une demande de connexion de [" << message-
>emitter().toString() << "] (client 1)... et l’accepte" << std::endl;
107. }
108. client.connect(message->emitter());
109. }
110. else if (message->is<Bousk::Network::Messages::Connection>())

- 27 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/Timeout/Main.cpp
111. {
112. if (message->emitter() != client1)
113. {
114. std::scoped_lock lock(coutMutex);
115. std::cout << "Connexion inattendue de " << message->emitter().toString() << " (devrait
être de " << client1.toString() << ")" << std::endl;
116. continue;
117. }
118. else
119. {
120. std::scoped_lock lock(coutMutex);
121. std::cout << "Client 1 [" << client1.toString() << "] connecté à client
2" << std::endl;
122. }
123. }
124. else if (message->is<Bousk::Network::Messages::Disconnection>())
125. {
126. std::scoped_lock lock(coutMutex);
127. assert(message->emitter() == client1);
128. std::cout << "Déconnexion de client 1...[" << message-
>as<Bousk::Network::Messages::Disconnection>()->reason << "]" << std::endl;
129. exit = true;
130. }
131. }
132. client.processSend();
133. std::this_thread::sleep_for(std::chrono::microseconds(1));
134. }
135. std::cout << "Fin de processus normal." << std::endl;
136. client.release();
137. });
138.
139. t1.join();
140. t2.join();
141.
142. Bousk::Network::Release();
143. return 0;
144. }

VI-C - Timeout de connexion

Enfin, un dernier test démontre le fonctionnement du timeout qui survient pendant la connexion :

Samples/UDP/ConnectionTimeout/Main.cpp
1. #include "Sockets.hpp"
2. #include "UDP/UDPClient.hpp"
3. #include "UDP/Protocols/ReliableOrdered.hpp"
4. #include "Serialization/Deserializer.hpp"
5. #include "Serialization/Serializer.hpp"
6. #include "Messages.hpp"
7. #include "Errors.hpp"
8.
9. #include <iostream>
10. #include <mutex>
11. #include <string>
12. #include <thread>
13.
14. int main()
15. {
16. if (!Bousk::Network::Start())
17. {
18. std::cout << " Erreur d’initialisation : " << Bousk::Network::Errors::Get(); return -1;
19. }
20.
21. const Bousk::Network::Address
client1 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 8888);
22. const Bousk::Network::Address
client2 = Bousk::Network::Address::Loopback(Bousk::Network::Address::Type::IPv4, 9999);
23.

- 28 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/ConnectionTimeout/Main.cpp
24. std::mutex coutMutex;
25. std::thread t1([&]()
26. {
27. Bousk::Network::UDP::Client client;
28. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
29. if (!client.init(client1.port()))
30. {
31. std::scoped_lock lock(coutMutex);
32. std::cout << "Client 1 erreur d’initialisation : " << Bousk::Network::Errors::Get();
33. return;
34. }
35. {
36. std::scoped_lock lock(coutMutex);
37. std::cout << "Client 1 initialisé sur le port " << client1.port() << std::endl;
38. }
39. client.connect(client2);
40. {
41. std::scoped_lock lock(coutMutex);
42. std::cout << "Client 1 connexion à " << client2.toString() << " en cours..." << std::endl;
43. }
44. std::vector<std::string> receivedMessages;
45. for (bool exit = false; !exit;)
46. {
47. client.receive();
48. auto messages = client.poll();
49. for (auto&& message : messages)
50. {
51. if (message->is<Bousk::Network::Messages::Connection>())
52. {
53. if (message->emitter() != client2)
54. {
55. std::scoped_lock lock(coutMutex);
56. std::cout << "[Client 1]Connexion inattendue de " << message-
>emitter().toString() << " (devrait être de " << client2.toString() << ")" << std::endl;
57. continue;
58. }
59. else
60. {
61. std::scoped_lock lock(coutMutex);
62. std::cout << "[Client 1]Connexion " << message-
>as<Bousk::Network::Messages::Connection>()->result << " de " << message-
>emitter().toString() << std::endl;
63. exit = true;
64. }
65. }
66. }
67. client.processSend();
68. std::this_thread::sleep_for(std::chrono::microseconds(1));
69. }
70. client.release();
71. });
72. std::thread t2([&]()
73. {
74. Bousk::Network::UDP::Client client;
75. client.registerChannel<Bousk::Network::UDP::Protocols::ReliableOrdered>();
76. if (!client.init(client2.port()))
77. {
78. std::scoped_lock lock(coutMutex);
79. std::cout << "Client 2 erreur d’initialisation : " << Bousk::Network::Errors::Get();
80. return;
81. }
82. {
83. std::scoped_lock lock(coutMutex);
84. std::cout << "Client 2 initialisé sur le port " << client2.port() << std::endl;
85. }
86. std::chrono::milliseconds timeoutStart = std::chrono::milliseconds(0);
87. constexpr auto Timeout = std::chrono::seconds(30); // Plus élevé que celui du moteur réseau
88. for (bool exit = false; !exit;)
89. {
90. client.receive();

- 29 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/
Cours programmation réseau en C++ par Bousk

Samples/UDP/ConnectionTimeout/Main.cpp
91. auto messages = client.poll();
92. for (auto&& message : messages)
93. {
94. if (message->is<Bousk::Network::Messages::IncomingConnection>())
95. {
96. if (message->emitter() != client1)
97. {
98. std::scoped_lock lock(coutMutex);
99. std::cout << "[Client 2]Connexion entrante inattendue de " << message-
>emitter().toString() << " (devrait être de " << client1.toString() << ")" << std::endl;
100. client.disconnect(message->emitter());
101. continue;
102. }
103. else
104. {
105. std::scoped_lock lock(coutMutex);
106. std::cout << "[Client 2]Connexion entrante de client 1 [" << message-
>emitter().toString() << "] et NON acceptée..." << std::endl;
107. timeoutStart = Bousk::Utils::Now();
108. }
109. }
110. else if (message->is<Bousk::Network::Messages::Connection>())
111. {
112. std::scoped_lock lock(coutMutex);
113. std::cout << "Connexion inattendue de " << message->emitter().toString() << std::endl;
114. }
115. else if (message->is<Bousk::Network::Messages::Disconnection>())
116. {
117. std::scoped_lock lock(coutMutex);
118. assert(message->emitter() == client1);
119. std::cout << "Déconnexion de " << message->emitter().toString() << "...[" << message-
>as<Bousk::Network::Messages::Disconnection>()->reason << "]" << std::endl;
120. exit = true;
121. } }
122. client.processSend();
123. std::this_thread::sleep_for(std::chrono::microseconds(1));
124. }
125. std::cout << "Fin de processus normal." << std::endl;
126. client.release();
127. });
128.
129. t1.join();
130. t2.join();
131.
132. Bousk::Network::Release();
133. return 0;
134. }

Article précédent
<< Sérialisation avancée Un premier jeu : Morpion / Tic-Tac-Toe

- 30 -
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 ® 2020 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.
https://bousk.developpez.com/cours/reseau-c++/UDP/08-gestion-connexions/

Vous aimerez peut-être aussi