NetworkingLibrary è una libreria in C++ progettata per gestire comunicazioni in un ambiente client-server. La libreria è suddivisa in tre componenti principali: Network, Client, e Server, che realizzano un'infrastruttura di rete avente comunicazione bidirezionale.
- Libreria: Questa parte gestisce il livello più basso dell'infrastruttura di rete e definisce classi astratte per client e server. Facilita la configurazione e personalizzazione dei vari componenti di rete.
- Client: Implementa la logica concreta per la comunicazione con il server, le operazioni client-side e gestisce il flusso di dati.
- Server: Gestisce le operazioni server-side e il flusso di dati tra i peer, permettendo una comunicazione che supporta più client connessi.
- Comunicazione TCP
- Socket non bloccanti
- Programmazione orientata agli oggetti
- Personalizzazione flessibile
- Sistema di pacchetti estendibile
- Monitoraggio dei socket tramite
Watcher - Interfaccia intuitiva per operazioni di rete
- Supporto per pacchetti con dimensioni sia statiche che dinamiche
-
Inizializzazione:
- Avvia la componente di rete (es.
WSAStartupsu Windows) e crea un socket. Il server ascolta su una porta specifica, mentre il client tenta di stabilire una connessione. - Il server gestisce connessioni multiple in modo asincrono e non bloccante, senza attendere il completamento delle operazioni I/O di ciascun client.
- Avvia la componente di rete (es.
-
Gestione delle Connessioni:
- Quando un client si connette, il server crea un
peerche rappresenta quella connessione, con un socket e un flusso dati associato. - La comunicazione avviene tramite pacchetti, definiti da strutture dati contenenti le informazioni da scambiare.
- Quando un client si connette, il server crea un
-
Monitoraggio degli Eventi sui Socket:
- Un
Watcher, basato sulla funzioneselect(), monitora gli eventi sui socket.
- Un
-
Gestione del Flusso di Dati:
- Il flusso di dati è gestito tramite buffer dedicati: uno per l'invio e uno per la ricezione. I dati sono scritti nel buffer di invio e inviati attraverso il socket, mentre i dati ricevuti sono accumulati nel buffer di ricezione fino alla loro analisi.
-
Fasi della Comunicazione:
- La comunicazione tra client e server si svolge attraverso diverse fasi, partendo dall'handshake iniziale.
-
Chiusura e Pulizia:
- La chiusura delle connessioni e la pulizia delle risorse di rete sono gestite per evitare perdite di risorse o chiusure improvvise.
Il progetto è organizzato come segue:
NetworkingLibrary/
├── Client/ # Contiene l'implementazione del client
├── Network/ # Contiene la libreria di rete
├── Server/ # Contiene l'implementazione del server
└── NetworkingLibrary.sln # File di soluzione per la compilazione del progetto
- Singleton
- Factory Method
- Abstract Factory
Il sistema consente di personalizzare i comportamenti mediante la sovrascrittura dei metodi nelle implementazioni concrete di client e server. Ecco alcuni esempi:
-
Personalizzazione del Client:
Implementa la classe
CAbstractClientper definire comportamenti specifici del client:class CustomClient : public CAbstractClient { public: void OnSocketCreated() override { // Codice per gestire la creazione del socket } void OnConnect() override { // Codice per gestire la connessione riuscita } void OnConnectFail() override { // Codice per gestire il fallimento della connessione } void OnDisconnect() override { // Codice per gestire la disconnessione } };
-
Personalizzazione del Server:
Implementa la classe
CAbstractServerper gestire eventi del server:class CustomServer : public CAbstractServer { public: void OnInitializeCompleted() override { // Codice per gestire il completamento dell'inizializzazione } void OnSocketListening() override { // Codice per gestire la fase in cui il socket inizia ad ascoltare } void OnConnectClient(std::shared_ptr<CSocket> newClientSocket) override { // Codice per gestire una nuova connessione client } void OnDisconnectClient(CAbstractPeer* peer) override { // Codice per gestire la disconnessione di un client } void DisconnectAll() override { // Codice per disconnettere tutti i client } };
-
Gestione dei Pacchetti:
Personalizza la gestione dei pacchetti implementando
CAbstractPacketManagere i gestori di pacchetti:class CustomPacketManager : public CAbstractPacketManager { public: void __LoadPacketHeaders() override { // Codice per caricare gli header dei pacchetti } }; class CustomPacketServerHandler : public CAbstractPacketServerHandler { public: bool Analyze(CAbstractPeer* peer, TPacketHeader header) override { // Codice per analizzare i pacchetti ricevuti dal server return true; } }; class CustomPacketClientHandler : public CAbstractPacketClientHandler { public: bool Analyze(TPacketHeader header) override { // Codice per analizzare i pacchetti ricevuti dal client return true; } };
Il sistema supporta la creazione di componenti personalizzati, come gestori di pacchetti e peer, tramite il pattern Factory. Ecco come definire e utilizzare una factory personalizzata:
-
Definire una Factory per il Client:
class CustomClientComponentsFactory : public Net::AbstractClientComponentsFactory { public: std::shared_ptr<Net::CAbstractPacketManager> CreatePacketManager() override { return std::make_shared<CustomPacketManager>(); } };
-
Definire una Factory per il Server:
class CustomServerComponentsFactory : public Net::AbstractServerComponentsFactory { public: std::shared_ptr<Net::CAbstractPacketManager> CreatePacketManager() override { return std::make_shared<CustomPacketManager>(); } std::unique_ptr<Net::CAbstractPeerManager> CreatePeerManager() override { return std::make_unique<CustomPeerManager>(); } std::shared_ptr<Net::CAbstractPeer> CreatePeer(std::shared_ptr<Net::SocketWatcher> serverWatcher) override { return std::make_shared<CustomPeer>(serverWatcher); } };
-
Utilizzare la Factory:
CAbstractServer server; CustomServerComponentsFactory factory; server.SetComponentsFactory(std::make_shared<CustomServerComponentsFactory>());
-
Definire la Nuova Fase in
Definition.h:enum EPhase { PHASE_CLOSE, PHASE_HANDSHAKE, PHASE_LOGIN, // Nuova fase aggiunta };
-
Impostare la Fase al Momento Opportuno:
bool ServerHandshake::RecvHandshake(CAbstractPeer* abstractPeer) { // ... if (peer->HandshakeProcess(handshakePacket.time, handshakePacket.delta)) { // Handshake completato, impostiamo la fase di login peer->SetPhase(PHASE_LOGIN); } // ... }
-
Creare una Nuova Classe per Gestire la Fase:
class ServerLogin : public Net::CAbstractPacketServerHandler { public: bool Analyze(Net::CAbstractPeer* peer, Net::TPacketHeader header) override; }; bool ServerLogin::Analyze(CAbstractPeer* peer, TPacketHeader header) { bool ret = true; switch (static_cast<PacketCSHeader>(header)) { // Gestione degli header dei pacchetti per questa fase. } return ret; }
-
Registrare l'Handler della Nuova Fase nella Classe
CPeer:class CPeer : public Net::CAbstractPeer { std::unique_ptr<ServerLogin> m_packetHandlerServerLogin; // Nuovo handler }; CPeer::CPeer(std::shared_ptr<Net::SocketWatcher> serverWatcher) : Net::CAbstractPeer(serverWatcher) { m_packetHandlerServerLogin = std::make_unique<ServerLogin>(); RegisterPhaseHandler(PHASE_LOGIN, m_packetHandlerServerLogin.get()); }
-
Definire l'Header del Nuovo Pacchetto in
PacketDefinition.h:enum class PacketCSHeader : TPacketHeader { HEADER_CS_LOGIN = 2, // Nuovo pacchetto per login (CS, client -> server) };
-
Definire la Struttura del Nuovo Pacchetto in
PacketDefinition.h:struct TPacketCSLogin { PacketCSHeader header = PacketCSHeader::HEADER_CS_LOGIN; char username[21]; // 20 caratteri + terminatore null char password[21]; // 20 caratteri + terminatore null };
-
Registrare il Pacchetto nella Classe
PacketManager:void CServerPacketManager::__LoadPacketHeaders() { DEFINE_PACKET(PacketCSHeader::HEADER_CS_LOGIN, TPacketCSLogin, STATIC_SIZE_PACKET); }
-
Analizzare e Gestire il Pacchetto nella Fase Appropriata:
bool ServerLogin::Analyze(CAbstractPeer* peer, TPacketHeader header) { bool ret = true; switch (static_cast<PacketCSHeader>(header)) { case PacketCSHeader::HEADER_CS_LOGIN: ret = RecvLogin(peer); break; } return ret; } bool ServerLogin::RecvLogin(CAbstractPeer* abstractPeer) { auto* peer = CPeerManager::ValidPeer<ServerPeerType>(abstractPeer); if (!peer) return false; TPacketCSLogin login; if (!CPacketIO::ReadPacketData(peer->GetSocket(), login)) return false; // Utilizzare i dati del pacchetto // login.username // login.password return true; }
Per dettagli specifici sull'architettura e le funzionalità di ciascun componente, consulta i README dei seguenti sottoprogetti:
- Sistema Operativo: Windows
- Ambiente di Sviluppo: Visual Studio 2019 (compilatore MSVC)
- Apri il file
NetworkingLibrary.slnpresente nella root del progetto. - Compila il progetto utilizzando Visual Studio 2019.
- Configurazione x64: Se compili con configurazione x64, l'output sarà nella cartella
x64presente nella root del progetto. Il binario si troverà in una sottocartella basata sul tipo di configurazione (Release o Debug). - Testato: Il progetto è stato testato su configurazione x64 sia in modalità Release che Debug.
Esempi di applicazioni includono:
- Giochi Multiplayer: Scambio di pacchetti di aggiornamento del gioco tra più giocatori.
- Sistemi di Chat: Invio e ricezione di messaggi tra utenti.
- Applicazioni Distribuite: Scambio continuo di dati tra nodi in una rete.
Per ulteriori dettagli e documentazione, consulta i README specifici dei sottoprogetti.