Skip to content
On this page

Protocole Réseau — R-Type Multiplayer Engine

1. Objectif

Le protocole réseau du projet R-Type vise à permettre la communication fiable et rapide entre les clients (joueurs) et le serveur central du jeu, en combinant deux modes de transport :

  • TCP pour les échanges critiques (connexion, login, gestion des rooms),
  • UDP pour les échanges temps réel (positions, tirs, collisions).

Cette approche garantit à la fois la fiabilité des échanges de contrôle et la performance du gameplay.


2. Architecture générale

Communication Hybride

CoucheProtocoleRôle
ContrôleTCPConnexion, authentification, création et gestion des rooms.
GameplayUDPEnvoi d'entrées et synchronisation d’état du jeu en temps réel.

Schéma simplifié

   TCP (Gestion / Connexion)
┌────────────┐                                ┌────────────┐
│   Client   │ <────────────────────────────> │   Serveur  │
└────────────┘                                └────────────┘
        ↑                                           ↑
        |                UDP (Gameplay)              |
        └──────────────→ (Entrées / États) ─────────┘
   TCP (Gestion / Connexion)
┌────────────┐                                ┌────────────┐
│   Client   │ <────────────────────────────> │   Serveur  │
└────────────┘                                └────────────┘
        ↑                                           ↑
        |                UDP (Gameplay)              |
        └──────────────→ (Entrées / États) ─────────┘
1
2
3
4
5
6
7

3. Phases de communication

Étape 1 — Connexion TCP et authentification

  1. Le client établit une connexion TCP avec le serveur (ServerHub).

  2. Le client envoie un LoginPacket contenant :

    • le nom du joueur,
    • un identifiant de session ou de version.
  3. Le serveur valide le login et peut retourner un RoomPacket pour informer le client qu’une room est prête à rejoindre.

Transport utilisé : TCPPaquet concerné : LoginPacket


Étape 2 — Attribution d’une Room

Lorsqu’un joueur rejoint ou crée une room :

  1. Le serveur attribue dynamiquement un port UDP pour cette room.

  2. Il envoie au client un RoomPacket contenant ce port :

    cpp
    class RoomPacket : public IPacket {
        public:
            uint16_t port;
            PacketType getType() const override { return PacketType::ROOMCONNECTION; }
    };
    
    class RoomPacket : public IPacket {
        public:
            uint16_t port;
            PacketType getType() const override { return PacketType::ROOMCONNECTION; }
    };
    
    1
    2
    3
    4
    5
  3. Le client utilise ce port pour créer un client UDP (ClientRoom) et passer en communication temps réel.

Transport utilisé : TCPPaquet concerné : RoomPacket


Étape 3 — Phase de jeu temps réel (UDP)

Une fois dans la room :

  • Le client envoie périodiquement des InputPacket :

    • ces paquets contiennent les actions du joueur (ex : mouvement, tir).
    cpp
    struct InputPacket : IPacket {
        uint8_t playerId;
        uint8_t inputMask; // bits correspondant aux actions (haut, bas, gauche, tir, etc.)
    };
    
    struct InputPacket : IPacket {
        uint8_t playerId;
        uint8_t inputMask; // bits correspondant aux actions (haut, bas, gauche, tir, etc.)
    };
    
    1
    2
    3
    4
  • Le serveur envoie en retour des GameStatePacket, qui contiennent :

    • les positions des entités,
    • les états des projectiles, ennemis, joueurs, etc.
    cpp
    struct GameStatePacket : IPacket {
        std::vector<EntityState> entities; // chaque entité = id, position, état
    };
    
    struct GameStatePacket : IPacket {
        std::vector<EntityState> entities; // chaque entité = id, position, état
    };
    
    1
    2
    3

Le serveur diffuse régulièrement ces informations afin de maintenir tous les joueurs synchronisés, même en cas de perte de paquets (UDP non fiable).

Transport utilisé : UDPPaquets concernés : InputPacket, GameStatePacket


Étape 4 — Fin de partie / déconnexion

Lorsqu’un joueur quitte la room :

  1. Le client envoie un DisconnectPacket.
  2. Le serveur retire le joueur de la room et notifie les autres joueurs.
  3. Si la room est vide, elle est détruite.

Transport utilisé : TCP ou UDP (selon l’état de la partie)Paquet concerné : DisconnectPacket


4. Format des paquets

Tous les paquets héritent de l’interface commune IPacket :

cpp
class IPacket {
public:
    virtual ~IPacket() = default;
    virtual PacketType getType() const = 0;
    virtual std::vector<uint8_t> serialize() const = 0;
    virtual void deserialize(const std::vector<uint8_t>& data) = 0;
};
class IPacket {
public:
    virtual ~IPacket() = default;
    virtual PacketType getType() const = 0;
    virtual std::vector<uint8_t> serialize() const = 0;
    virtual void deserialize(const std::vector<uint8_t>& data) = 0;
};
1
2
3
4
5
6
7

En-tête standard des paquets binaires

ChampTailleDescription
Type1 octetIdentifie le type du paquet (PacketType)
Taille2 octetsTaille du corps du paquet
DonnéesvariableDonnées spécifiques (texte, entités, états, etc.)

Exemple de sérialisation

cpp
std::vector<uint8_t> LoginPacket::serialize() const {
    std::vector<uint8_t> data;
    data.push_back(static_cast<uint8_t>(PacketType::LOGIN));
    appendString(data, username);
    appendString(data, version);
    return data;
}
std::vector<uint8_t> LoginPacket::serialize() const {
    std::vector<uint8_t> data;
    data.push_back(static_cast<uint8_t>(PacketType::LOGIN));
    appendString(data, username);
    appendString(data, version);
    return data;
}
1
2
3
4
5
6
7

Exemple de désérialisation

cpp
void LoginPacket::deserialize(const std::vector<uint8_t>& data) {
    size_t offset = 1;
    username = readString(data, offset);
    version = readString(data, offset);
}
void LoginPacket::deserialize(const std::vector<uint8_t>& data) {
    size_t offset = 1;
    username = readString(data, offset);
    version = readString(data, offset);
}
1
2
3
4
5

5. Gestion centralisée des paquets

Deux classes assurent la modularité et l’évolutivité du protocole :

ClasseRôle
PacketsFactoryCrée le bon type de paquet selon le type lu dans le header.
PacketsManagerSérialise et désérialise les paquets de manière uniforme.

Exemple

cpp
auto login = LoginPacket("Player1", "v1.0", "R-Type");
auto bytes = packetManager.buildPacket(login);
auto parsed = packetManager.parsePacket(bytes);
auto login = LoginPacket("Player1", "v1.0", "R-Type");
auto bytes = packetManager.buildPacket(login);
auto parsed = packetManager.parsePacket(bytes);
1
2
3

Cette approche permet d’ajouter de nouveaux paquets sans modifier le cœur réseau.


6. Communication asynchrone (Boost.Asio)

Le protocole repose sur un modèle asynchrone non-bloquant, grâce à Boost.Asio :

Exemple côté serveur

cpp
acceptor.async_accept(socket, handler);
acceptor.async_accept(socket, handler);
1
  • Permet de gérer plusieurs connexions simultanément (multiclient TCP).
  • Chaque client possède son propre thread io_context.

Exemple côté client

cpp
socket.async_receive_from(buffer, sender_endpoint, handler);
socket.async_receive_from(buffer, sender_endpoint, handler);
1
  • Permet la réception continue sans bloquer le flux d’entrée.
  • Combine un thread io.run() et un thread d’entrée utilisateur.

7. Intégrité et sécurité

Le protocole prévoit des mécanismes d’extension pour :

  • compression des paquets (réduction du trafic),
  • chiffrement (protection des données sensibles comme le login),
  • vérification CRC / checksum pour détecter les paquets corrompus.

Ces fonctionnalités sont optionnelles mais prêtes à l’intégration via les utilitaires encrypt.cpp et compress.cpp.


8. Séquence de communication typique

[Client] ---------------------------------------- TCP ----------------------------------------> [Serveur]
           LoginPacket { username="Player1", version="R-Type" }

[Serveur] <-------------------------------------- TCP ----------------------------------------- [Client]
           RoomPacket { port=4000 }

[Client] ---------------------------------------- UDP ----------------------------------------> [Serveur]
           InputPacket { inputMask=0b1010 }

[Serveur] <-------------------------------------- UDP ----------------------------------------- [Client]
           GameStatePacket { entities=[player1, enemy, bullet...] }

[Client] ---------------------------------------- TCP ----------------------------------------> [Serveur]
           DisconnectPacket
[Client] ---------------------------------------- TCP ----------------------------------------> [Serveur]
           LoginPacket { username="Player1", version="R-Type" }

[Serveur] <-------------------------------------- TCP ----------------------------------------- [Client]
           RoomPacket { port=4000 }

[Client] ---------------------------------------- UDP ----------------------------------------> [Serveur]
           InputPacket { inputMask=0b1010 }

[Serveur] <-------------------------------------- UDP ----------------------------------------- [Client]
           GameStatePacket { entities=[player1, enemy, bullet...] }

[Client] ---------------------------------------- TCP ----------------------------------------> [Serveur]
           DisconnectPacket
1
2
3
4
5
6
7
8
9
10
11
12
13
14

9. Typologie des paquets

TypeCode HexDescriptionTransport
LOGIN0x01Authentification du joueurTCP
LOGOUT0x02Déconnexion propreTCP
ROOMCONNECTION0x03Informations pour rejoindre une roomTCP
INPUT0x10Entrées joueur (clavier / manette)UDP
GAME_STATE0x11État global du jeu (positions, etc.)UDP
SPAWN_ENTITY0x12Apparition d’une entitéUDP
DESTROY_ENTITY0x13Suppression d’une entitéUDP
UPDATE_ENTITY0x14Mise à jour d’une entitéUDP
PING0x20Vérification de latenceTCP
PONG0x21Réponse au pingTCP
ERROR_PACKET0xFFPaquet invalide ou non reconnuTCP

10. Gestion des Rooms

  • Chaque Room correspond à un serveur UDP distinct, créé à la demande.
  • Le port UDP est communiqué au client via un RoomPacket.
  • Le client fork ou lance un nouveau processus dédié (ClientRoom) pour gérer la partie.

Avantages :

  • Isolation complète entre les parties.
  • Possibilité d’avoir plusieurs rooms en parallèle.
  • Évolutif (un serveur peut héberger plusieurs rooms simultanément).

11. Résumé

ÉtapePaquets utilisésProtocoleRôle
AuthentificationLoginPacketTCPConnexion du joueur
Attribution de roomRoomPacketTCPRedirection vers un serveur de jeu
GameplayInputPacket, GameStatePacketUDPSynchronisation temps réel
Fin de partieDisconnectPacketTCPFermeture propre

12. Points clés du design

  • TCP pour la fiabilité, UDP pour la rapidité.
  • Architecture modulaire (chaque paquet est une classe autonome).
  • Sérialisation binaire uniforme et extensible.
  • Communication asynchrone et non bloquante.
  • Prête pour le multithreading et la montée en charge.