Déboguer une application est primordial pour assurer sa stabilité et son bon fonctionnement.
Il y a cependant un problème majeur lorsque l’application a une composante réseau, en particulier si elle utilise un système de connexion via UDP : quand une application rencontre un point d’arrêt et que le débogueur s’arrête, les mécanismes de perte de connexion sont rapidement déclenchés. Le TCP est moins contraignant puisque le protocole prendra généralement plus de temps à détecter ceci comme une déconnexion.
Ceci peut complexifier énormément la tâche, puisqu’une déconnexion entraîne souvent la perte de la session et donc de l’état du programme.
I-A. Comment y remédier?▲
Afin de permettre un travail plus efficace, il est intéressant de modifier le mécanisme de déconnexion pour ne pas déclencher une déconnexion, mais seulement générer une interruption de connexion. À l’inverse d’une déconnexion, une connexion juste interrompue pourrait reprendre.
Ce mécanisme ne sera généralement activé que dans un environnement de développement et non dans l’application publique finale où l’on préférera laisser les déconnexions en cas d’interruption du flux de données, quelle que soit la cause réelle, afin de limiter les frictions envers les joueurs restants.
L’application sera ensuite libre de gérer cette interruption de connexion comme elle le souhaite. Dans la version de développement, il s’agira de mettre en pause la mise à jour des éléments du jeu permettant ainsi de vérifier ce que l’on veut et comprendre pourquoi un bug survient ou pour quelle raison le jeu ou une partie de celui-ci est tombé dans un état particulier.
L’interruption de la connexion sera matérialisée par un message généré par le moteur réseau pour l’application.
À la réception de ce message, l’application peut choisir comment réagir et déclencher les mécanismes d’interruption qu’elle souhaite. Dans un jeu, on pourra décider de déclencher une pause et arrêter de mettre à jour les unités et éléments gameplay afin de garder l’état actuel en mémoire et pouvoir effectuer les vérifications de son choix. Ou tout simplement pour ne pas continuer de faire évoluer la partie alors qu’une machine de la session ne répond plus, par exemple parce que celle-ci vient de rencontrer un point d’arrêt pour étudier un bug. Une fois tout en ordre, et si cela est possible, alors la machine interrompue reprend son exécution, cette reprise est détectée par l’ensemble des machines et la session peut continuer son cours.
Deux types d’interruption sont possibles : une interruption directement constatée avec un client ou une interruption reportée par un client avec un de ses propres clients.
II-A. Interruption directe▲
L’interruption directe sera celle constatée directement vis-à-vis du client perdu : plus aucune donnée n’est reçue de ce client depuis suffisamment longtemps pour le considérer comme interrompu.
II-B. Interruption indirecte▲
L’interruption indirecte sera une interruption reçue d’un client non interrompu, mais dont l’un de ses clients est interrompu directement. C’est une interruption qui est transmise à ses clients et qui est donc reçue et non constatée :
Ou indirectement selon la complexité du réseau :
II-C. Une interruption unidirectionnelle▲
Notez que l’interruption n’est pas bidirectionnelle : le client qui constate l’interruption continue d’envoyer des données vers le client interrompu.
Ceci afin que la connexion puisse reprendre dès que possible et pour éviter un deadlock le cas échéant :
- A arrête ses envois vers B ;
- B déclare A interrompu ;
- si B arrête ses envois vers A, alors quand A reprend, il déclare B interrompu.
De la même façon, B ne doit pas non plus transmettre une interruption à A si seul A est interrompu.
II-D. Reprise de la connexion▲
Si l’interruption est constatée par un arrêt de réception des données ou par une interruption transmise par un client, alors la reprise sera effective quand des données recommenceront à être reçues ou quand une reprise de connexion sera indirectement reçue.
Si plusieurs interruptions sont constatées, alors la reprise complète du moteur réseau survient quand toutes les connexions ont repris.
II-E. Se débarrasser d’un client▲
Il n’est parfois pas possible qu’un client puisse rétablir sa connexion. Un client peut avoir totalement crashé, ou l’application a été fermée et n’existe plus.
On peut aussi vouloir continuer sans attendre le client et simplement vouloir terminer notre connexion avec lui.
Dans ce cas, il est nécessaire de se débarrasser du client de force avec un système de « kick ».
Il s’agit essentiellement de modifier le comportement des déconnexions et du maintien de connexion. Nous nous appuierons donc sur les implémentations du chapitre 8 : gestion de connexions.
III-A. Une fonctionnalité désactivable▲
Cette fonctionnalité sera désactivable grâce au préprocesseur afin de pouvoir la retirer complètement de l’exécutable final.
En effet, bien que très utile pendant la phase de développement, elle pourrait rapidement devenir ennuyeuse pour les utilisateurs finaux.
Ainsi, tout le code spécifique à cette fonctionnalité sera protégé par des instructions préprocesseurs.
La syntaxe utilisée reprendra celle existante dans le fichier Settings.hpp et elle sera désactivée par défaut :
#ifndef BOUSKNET_ALLOW_NETWORK_INTERRUPTION
#define BOUSKNET_ALLOW_NETWORK_INTERRUPTION BOUSKNET_SETTINGS_DISABLED
#endif
III-B. Messages▲
Commençons par ajouter les nouveaux messages pour supporter ce mécanisme.
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
class
ConnectionInterrupted : public
Base
{
DECLARE_MESSAGE(ConnectionInterrupted);
public
:
ConnectionInterrupted(const
Address&
emitter, uint64 emitterid, bool
isDirect)
:
Base(Type::
ConnectionInterrupted, emitter, emitterid)
, isDirectInterruption(isDirect)
{}
bool
isDirectInterruption;
}
;
class
ConnectionResumed : public
Base
{
DECLARE_MESSAGE(ConnectionResumed);
public
:
ConnectionResumed(const
Address&
emitter, uint64 emitterid, bool
networkResume)
:
Base(Type::
ConnectionResumed, emitter, emitterid)
, isNetworkResumed(networkResume)
{}
bool
isNetworkResumed;
}
;
#endif
III-C. Mise à jour du code▲
III-C-1. UDP Client▲
Commençons par mettre à jour le client UDP. Ce dernier devra suivre les interruptions en cours et permettre d’informer si la connexion est actuellement interrompue ou non, et, en cas d’interruption, de dire si le client est le seul coupable ou non de l’interruption.
III-C-1-a. Interface▲
Voici l’interface qui permettra de modifier l’état de l’interruption et de retrouver les informations qui nous intéressent :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
void
onClientInterrupted(const
DistantClient*
client);
void
onClientResumed(const
DistantClient*
client);
bool
isInterruptionCulprit(const
DistantClient*
client) const
;
#endif
Quelques variables supplémentaires sont nécessaires pour y parvenir :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
bool
mNetworkInterruptionAllowed{
true
}
;
std::
unordered_set<
const
DistantClient*>
mInterruptedClients;
#endif
Puis quelques fonctions pour contrôler cette fonctionnalité :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
inline
void
enableNetworkInterruption() {
setNetworkInterruptionEnabled(true
); }
inline
void
disableNetworkInterruption() {
setNetworkInterruptionEnabled(false
); }
inline
void
setNetworkInterruptionEnabled(bool
enabled) {
mNetworkInterruptionAllowed =
enabled; }
inline
bool
isNetworkInterruptionAllowed() const
{
return
mNetworkInterruptionAllowed; }
inline
bool
isNetworkInterrupted() const
{
return
!
mInterruptedClients.empty(); }
#endif
III-C-1-b. Implémentations▲
Les implémentations de ces fonctions sont assez simples et directes :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
void
Client::
onClientInterrupted(const
DistantClient*
client)
{
mInterruptedClients.insert(client);
}
void
Client::
onClientResumed(const
DistantClient*
client)
{
mInterruptedClients.erase(client);
}
bool
Client::
isInterruptionCulprit(const
DistantClient*
client) const
{
return
mInterruptedClients.size() ==
1
&&
*
(mInterruptedClients.begin()) ==
client;
}
#endif
III-C-2. Client distant▲
III-C-2-a. Interface▲
Puis mettons à jour la détection de la déconnexion vers une détection d’interruption dans le client distant.
Définissons d’abord l’interface que nous voulons utiliser :
void
onConnectionInterrupted();
void
onConnectionInterruptedForwarded();
void
onConnectionResumed();
Et des variables membres pour supporter cette fonctionnalité :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
bool
mInterrupted{
false
}
;
bool
mDistantInterrupted{
false
}
;
#endif
Ainsi que des accesseurs pour ces variables :
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
inline
bool
isInterrupted() const
{
return
mInterrupted; }
inline
bool
hasInterruptedClients() const
{
return
mDistantInterrupted; }
#endif
III-C-2-b. Implémentations▲
III-C-2-b-i. Interruption directe▲
Puisque l’objectif est de modifier une perte de connexion en connexion interrompue, il est assez aisé de trouver où se brancher dans le code pour cela.
Trouvons où onConnectionLost() est appelé pour le modifier par onConnectionInterrupted(). Cette fonction devra déclencher l’interruption directe si cette fonctionnalité est activée, sinon une déconnexion :
void
DistantClient::
onConnectionInterrupted()
{
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
if
(mClient.isNetworkInterruptionAllowed())
{
if
(!
mInterrupted)
{
mInterrupted =
true
;
mClient.onClientInterrupted(this
);
onMessageReady(std::
make_unique<
Messages::
ConnectionInterrupted>
(mAddress, mClientId, true
));
}
}
else
#endif
{
if
(isConnected())
{
onConnectionLost();
}
}
}
III-C-2-b-ii. Interruption indirecte▲
Maintenant, prenons en compte les interruptions transmises.
Si elles sont reçues, elles doivent l’être via un datagramme ou paquet.
Nous allons donc ajouter cette information dans le datagramme de keep-alive. Ce keep-alive nous informe généralement que la connexion est correcte. Ici, nous devons modifier son traitement pour tenir compte du fait qu’elle peut nous transmettre une interruption :
void
DistantClient::
maintainConnection(bool
distantNetworkInterrupted )
{
mLastKeepAlive =
Utils::
Now();
if
(distantNetworkInterrupted)
onConnectionInterruptedForwarded();
else
onConnectionResumed();
}
L’implémentation de onConnectionResumed sera détaillée juste après, et onConnectionInterruptedForwarded sera comme ceci :
void
DistantClient::
onConnectionInterruptedForwarded()
{
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
if
(mClient.isNetworkInterruptionAllowed())
{
if
(!
mDistantInterrupted)
{
if
(mInterrupted)
{
mInterrupted =
false
;
onMessageReady(std::
make_unique<
Messages::
ConnectionResumed>
(mAddress, mClientId, false
));
}
mDistantInterrupted =
true
;
mClient.onClientInterrupted(this
);
onMessageReady(std::
make_unique<
Messages::
ConnectionInterrupted>
(mAddress, mClientId, false
));
}
}
#endif
}
Puis nous modifions la gestion du keep-alive pour prendre en compte ce nouveau mécanisme :
void
DistantClient::
handleKeepAlive(const
uint8*
data, const
uint16 datasize)
{
...
bool
isNetworkInterruptedOnTheOtherEnd =
false
;
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
deserializer.read(isNetworkInterruptedOnTheOtherEnd);
#endif
maintainConnection(isNetworkInterruptedOnTheOtherEnd);
}
III-C-2-b-iii. Reprise de la connexion▲
Maintenant que l’interruption est gérée, il faut gérer le mécanisme inverse : la reprise de la connexion.
Commençons par la fonction de plus haut niveau que nous avons déjà rencontrée dans la partie précédente et qui génère le message de reprise pour l’application :
void
DistantClient::
onConnectionResumed()
{
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
if
(mInterrupted ||
mDistantInterrupted)
{
mInterrupted =
false
;
mDistantInterrupted =
false
;
mClient.onClientResumed(this
);
onMessageReady(std::
make_unique<
Messages::
ConnectionResumed>
(mAddress, mClientId, !
mClient.isNetworkInterrupted()));
}
#endif
}
Afin de ne pas bloquer l’application, nous pouvons également considérer qu’une connexion sur le point de disparaître (un client qui va se déconnecter) reprend juste avant :
void
DistantClient::
processSend(const
uint8 maxDatagrams )
{
...
if
(isDisconnecting())
{
if
(now >
mLastKeepAlive +
2
*
GetTimeout())
{
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
if
(isInterrupted())
onConnectionResumed();
#endif
Et bien sûr, il faut modifier le keep-alive afin d’intégrer ce nouveau mécanisme dans son envoi également :
void
DistantClient::
fillKeepAlive(Datagram&
dgram)
{
fillDatagramHeader(dgram, Datagram::Type::
KeepAlive);
Serialization::
Serializer serializer;
serializer.write(mState ==
State::
ConnectionSent ||
isConnected());
#if BOUSKNET_ALLOW_NETWORK_INTERRUPTION == BOUSKNET_SETTINGS_ENABLED
const
bool
isNetworkInterrupted =
mClient.isNetworkInterrupted();
const
bool
isNetworkInterruptedByMe =
mClient.isInterruptionCulprit(this
);
serializer.write(isNetworkInterrupted &&
!
isNetworkInterruptedByMe);
#endif
memcpy(dgram.data.data(), serializer.buffer(), serializer.bufferSize());
dgram.datasize =
static_cast
<
uint16>
(serializer.bufferSize());
}
III-D. Kick▲
Pour se débarrasser d’un client, nous n’avons pas besoin d’ajouter un mécanisme de kick particulier : nous pouvons simplement utiliser l’interface disconnect du client UDP.
Enfin, des tests sont écrits pour s’assurer que tout ceci fonctionne comme attendu.
IV-A. Interruption et reprise▲
Le premier test vérifiera que les mécanismes d’interruption et de reprise sont fonctionnels.
Pour ceci, nous créons et connectons deux clients, puis l’un des clients cesse d’envoyer des données afin d’être détecté comme interrompu par l’autre client.
Enfin, l’envoi de données reprend, et la connexion doit être notifiée comme reprise :
ConnectionInterruption_Test.cpp
void
ConnectionInterruption_Test::
TestInterruptionAndResume()
{
const
Bousk::Network::
Address client1 =
Bousk::Network::Address::
Loopback(Bousk::Network::Address::Type::
IPv4, 8888
);
const
Bousk::Network::
Address client2 =
Bousk::Network::Address::
Loopback(Bousk::Network::Address::Type::
IPv4, 9999
);
const
std::
vector<
std::
string>
messagesToSend =
{
"my"
, "first"
, "udp"
, "transfert"
}
;
std::
mutex coutMutex;
std::
thread t1([&
]()
{
Bousk::Network::UDP::
Client client;
client.registerChannel<
Bousk::Network::UDP::Protocols::
ReliableOrdered>
();
client.enableNetworkInterruption();
if
(!
client.init(client1.port()))
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 initialisation error : "
<<
Bousk::Network::Errors::
Get();
return
;
}
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 initialized on port "
<<
client1.port() <<
std::
endl;
}
client.connect(client2);
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 connecting to "
<<
client2.toString() <<
"..."
<<
std::
endl;
}
std::
vector<
std::
string>
receivedMessages;
for
(bool
exit =
false
; !
exit;)
{
client.receive();
auto
messages =
client.poll();
for
(auto
&&
message : messages)
{
if
(message->
is<
Bousk::Network::Messages::
Connection>
())
{
if
(message->
emitter() !=
client2)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client2.toString() <<
")"
<<
std::
endl;
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 ["
<<
client2.toString() <<
"] connected to client 1"
<<
std::
endl;
std::
cout <<
"Sleeping 10s to trigger interruption..."
<<
std::
endl;
}
std::this_thread::
sleep_for(std::chrono::
seconds(10
));
}
else
if
(message->
is<
Bousk::Network::Messages::
UserData>
())
{
const
Bousk::Network::Messages::
UserData*
userdata =
message->
as<
Bousk::Network::Messages::
UserData>
();
Bousk::Serialization::
Deserializer deserializer(userdata->
data.data(), userdata->
data.size());
std::
string msg;
if
(!
deserializer.read(msg))
{
std::
cout <<
"Error deserializing msg !"
<<
std::
endl;
return
;
}
receivedMessages.push_back(msg);
if
(receivedMessages ==
messagesToSend)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Every messages received in order !"
<<
std::
endl;
std::
cout <<
"Disconnecting client 2..."
<<
std::
endl;
client.disconnect(client2);
}
}
else
if
(message->
is<
Bousk::Network::Messages::
Disconnection>
())
{
assert(message->
emitter() ==
client2);
std::
cout <<
"Shutting down client 1..."
<<
std::
endl;
exit =
true
;
}
}
client.processSend();
std::this_thread::
sleep_for(std::chrono::
microseconds(1
));
}
std::
cout <<
"[Client 1]Normal termination."
<<
std::
endl;
client.release();
}
);
std::
thread t2([&
]()
{
Bousk::Network::UDP::
Client client;
client.registerChannel<
Bousk::Network::UDP::Protocols::
ReliableOrdered>
();
client.enableNetworkInterruption();
if
(!
client.init(client2.port()))
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 initialisation error : "
<<
Bousk::Network::Errors::
Get();
return
;
}
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 initialized on port "
<<
client2.port() <<
std::
endl;
}
for
(bool
connected =
false
, exit =
false
; !
exit;)
{
client.receive();
auto
messages =
client.poll();
for
(auto
&&
message : messages)
{
if
(message->
is<
Bousk::Network::Messages::
IncomingConnection>
())
{
if
(message->
emitter() !=
client1)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection received from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client1.toString() <<
")"
<<
std::
endl;
client.disconnect(message->
emitter());
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 receiving incoming connection from ["
<<
message->
emitter().toString() <<
"] (client 1)... and accepting it"
<<
std::
endl;
}
client.connect(message->
emitter());
}
else
if
(message->
is<
Bousk::Network::Messages::
Connection>
())
{
if
(message->
emitter() !=
client1)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client1.toString() <<
")"
<<
std::
endl;
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 ["
<<
client1.toString() <<
"] connected to client 2"
<<
std::
endl;
}
for
(const
auto
&
msg : messagesToSend)
{
Bousk::Serialization::
Serializer serializer;
if
(!
serializer.write(msg))
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Error serializing msg
\"
"
<<
msg <<
"
\"
!"
<<
std::
endl;
return
;
}
std::
vector<
Bousk::
uint8>
buffer(serializer.buffer(), serializer.buffer() +
serializer.bufferSize());
client.sendTo(client1, std::
move(buffer), 0
);
}
connected =
true
;
}
else
if
(connected)
{
if
(message->
is<
Bousk::Network::Messages::
ConnectionInterrupted>
())
{
const
Bousk::Network::Messages::
ConnectionInterrupted*
msg =
message->
as<
Bousk::Network::Messages::
ConnectionInterrupted>
();
std::
cout <<
"Connection interrupted with client 1..."
<<
std::
endl;
}
else
if
(message->
is<
Bousk::Network::Messages::
ConnectionResumed>
())
{
const
Bousk::Network::Messages::
ConnectionResumed*
msg =
message->
as<
Bousk::Network::Messages::
ConnectionResumed>
();
std::
cout <<
"Connection resumed "
<<
(msg->
isNetworkResumed ? "totally"
: "partially"
) <<
" with client 1..."
<<
std::
endl;
}
else
if
(message->
is<
Bousk::Network::Messages::
Disconnection>
())
{
std::
scoped_lock lock(coutMutex);
assert(message->
emitter() ==
client1);
std::
cout <<
"Disconnection from client 1... ["
<<
message->
as<
Bousk::Network::Messages::
Disconnection>
()->
reason <<
"]"
<<
std::
endl;
exit =
true
;
}
}
}
client.processSend();
std::this_thread::
sleep_for(std::chrono::
microseconds(1
));
}
std::
cout <<
"[Client 2]Normal termination."
<<
std::
endl;
client.release();
}
);
t1.join();
t2.join();
}
IV-B. Interruption et kick▲
Un second test vérifiera que quand un client interrompu se fait « kick », alors la connexion reprend normalement pour le client restant :
ConnectionInterruption_Test.cpp
void
ConnectionInterruption_Test::
TestInterruptionAndKick()
{
const
Bousk::Network::
Address client1 =
Bousk::Network::Address::
Loopback(Bousk::Network::Address::Type::
IPv4, 8888
);
const
Bousk::Network::
Address client2 =
Bousk::Network::Address::
Loopback(Bousk::Network::Address::Type::
IPv4, 9999
);
std::
mutex coutMutex;
std::
thread t1([&
]()
{
Bousk::Network::UDP::
Client client;
client.registerChannel<
Bousk::Network::UDP::Protocols::
ReliableOrdered>
();
client.enableNetworkInterruption();
if
(!
client.init(client1.port()))
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 initialisation error : "
<<
Bousk::Network::Errors::
Get();
return
;
}
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 initialized on port "
<<
client1.port() <<
std::
endl;
}
client.connect(client2);
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 connecting to "
<<
client2.toString() <<
"..."
<<
std::
endl;
}
std::
vector<
std::
string>
receivedMessages;
for
(bool
exit =
false
; !
exit;)
{
client.receive();
auto
messages =
client.poll();
for
(auto
&&
message : messages)
{
if
(message->
is<
Bousk::Network::Messages::
Connection>
())
{
if
(message->
emitter() !=
client2)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client2.toString() <<
")"
<<
std::
endl;
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 ["
<<
client2.toString() <<
"] connected to client 1"
<<
std::
endl;
std::
cout <<
"Shutting down client 1!"
<<
std::
endl;
}
exit =
true
;
}
}
client.processSend();
std::this_thread::
sleep_for(std::chrono::
microseconds(1
));
}
std::
cout <<
"[Client 1]Normal termination."
<<
std::
endl;
client.release();
}
);
std::
thread t2([&
]()
{
Bousk::Network::UDP::
Client client;
client.registerChannel<
Bousk::Network::UDP::Protocols::
ReliableOrdered>
();
client.enableNetworkInterruption();
if
(!
client.init(client2.port()))
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 initialisation error : "
<<
Bousk::Network::Errors::
Get();
return
;
}
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 initialized on port "
<<
client2.port() <<
std::
endl;
}
for
(bool
connected =
false
, exit =
false
; !
exit;)
{
client.receive();
auto
messages =
client.poll();
for
(auto
&&
message : messages)
{
if
(message->
is<
Bousk::Network::Messages::
IncomingConnection>
())
{
if
(message->
emitter() !=
client1)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection received from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client1.toString() <<
")"
<<
std::
endl;
client.disconnect(message->
emitter());
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 2 receiving incoming connection from ["
<<
message->
emitter().toString() <<
"] (client 1)... and accepting it"
<<
std::
endl;
}
client.connect(message->
emitter());
}
else
if
(message->
is<
Bousk::Network::Messages::
Connection>
())
{
if
(message->
emitter() !=
client1)
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Unexpected connection from "
<<
message->
emitter().toString() <<
" (should be from "
<<
client1.toString() <<
")"
<<
std::
endl;
continue
;
}
else
{
std::
scoped_lock lock(coutMutex);
std::
cout <<
"Client 1 ["
<<
client1.toString() <<
"] connected to client 2"
<<
std::
endl;
}
connected =
true
;
}
else
if
(connected)
{
if
(message->
is<
Bousk::Network::Messages::
ConnectionInterrupted>
())
{
const
Bousk::Network::Messages::
ConnectionInterrupted*
msg =
message->
as<
Bousk::Network::Messages::
ConnectionInterrupted>
();
std::
cout <<
"Connection interrupted with client 1..."
<<
std::
endl;
std::
cout <<
"Disconnecting/Kicking client 1..."
<<
std::
endl;
client.disconnect(client1);
}
else
if
(message->
is<
Bousk::Network::Messages::
ConnectionResumed>
())
{
const
Bousk::Network::Messages::
ConnectionResumed*
msg =
message->
as<
Bousk::Network::Messages::
ConnectionResumed>
();
std::
cout <<
"Connection resumed "
<<
(msg->
isNetworkResumed ? "totally"
: "partially"
) <<
" with client 1..."
<<
std::
endl;
}
else
if
(message->
is<
Bousk::Network::Messages::
Disconnection>
())
{
std::
scoped_lock lock(coutMutex);
assert(message->
emitter() ==
client1);
std::
cout <<
"Disconnection from client 1... ["
<<
message->
as<
Bousk::Network::Messages::
Disconnection>
()->
reason <<
"]"
<<
std::
endl;
exit =
true
;
}
}
}
client.processSend();
std::this_thread::
sleep_for(std::chrono::
microseconds(1
));
}
std::
cout <<
"[Client 2]Normal termination."
<<
std::
endl;
client.release();
}
);
t1.join();
t2.join();
}
Retrouvez le code source dans le dépôt GitHub.