Salve ragazzi! E' arrivato il giorno in cui devo cestinare il prototipo e fare una versiona "alpha" delle mie funzioni di networking.
Fin'ora per il prototipo non mi è interessata la stabilità, volevo giusto qualcosa che funzionasse per poco. Ora mi serve una stabilità indipendente dal client, che sia veloce o meno (il port tra windows e linux lo so già fare).
Se potete, aiutatemi con questo problema:
Ho conoscenze basse del TCP e UDP. Per ora, utilizzerò il tcp, sia come sfida personale (sembra più difficile) sia perché non posso permettermi di perdere qualità dei dati con il videogame, probabilmente in futuro mischierò l'utilizzo dei due protocolli. Ciò che so fare è solo spedire e mandare, infatti nella mia funzione per la communicazione tra il client e il server (in entrambe le applicazioni) si alterna tra un "ricevi" e "manda", e quando riceve crea un vettore con tutti i dati elaborati dal buffer ricevuto. Non utilizzo, attualmente, neanche un invio di quanti byte è lunga la stringa (sembra anche funzionare più del dovuto, regge decine di minuti senza provocare problemi: ogni fine di ricezione, la stringa è completa e non è mischiata con nessun'altra). Utilizzo anche i socket blocking, e credo che questo possa creare dei deadlock. Ciò che mi servirebbe sarebbe una funzione che manda e riceve in modo asincrono (SENZA usare boost: dovrei rifare completamente il server, senza contare che è una questione di principio: voglio capire cosa sto usando).
Attualmente utilizzo i socket SFML, ma so usare anche i socket winsock e i socket linux (i socket sfml sono più facili e uguali tra gli OS, ho comunque appreso anche i socket classici perché, come ho detto prima, è una questione di principio, devo sapere cosa sto usando e come funziona qualcosa prima di usare la sua versione facile), e questa è la mia funzione, che lancio come thread per ogni client (ATTENZIONE: ALCUNE VARIABILI SONO GLOBALI, COME I CLIENT CONTENENTI I SOCKET):
//SERVER
void communicationThread(int id) //Id del client, i client sono un array di client
{
Socket::Status status = Socket::Done; //Le SFML hanno una classe status, può essere "Done" per "ricevuto tutto correttamente", "Not Ready", "Partial" (con i socket non-blocking), "Error" o "Disconnected".
string inputString, outputString; //La stringa inputString è la stringa che verrà caricata con il buffer e successivamente elaborata, l'outputString la stringa che verrà caricata con i dati del server e trasformata in buffer
string data[50]; //Il vettore che conterrà i dati ricevuti elaborati
stringstream ss;
list<ServerStatus>::iterator sStatus; //Iterator per gli snapshot del server, il sistema snapshot è il mio metodo di gestione del multithreading
char buffer[MAX_BUFFER]; //Non ho sinceramente idea di che valore devo dare a buffer, quindi dò 50 000 e sembra funzionare... Con 5000 ogni tanto dà SIGSEGV
size_t received; //La variabile per i bytes ricevuti
client[id].socket.setBlocking(true); // <- si spiega da sola
while(status == Socket::Done || status == Socket::NotReady) //Finchè non ci sono errori (NotReady non lo considero un errore)
{
/*RECEIVE PHASE******************************/
memset(buffer, '\0', MAX_BUFFER);
status = client[id].socket.receive(buffer, MAX_BUFFER, received); //Ricezione socket SFML con assegnazione dello status alla variabile status
if (status == Socket::Done || status == Socket::NotReady) //Se non ci sono errori
{
if (!client[id].input.readable && status == Socket::Done && buffer[0] != '\0') //Se il buffer non è vuoto e posso leggere l'input dal file (anche altri thread leggono questi dati, quindi si alternano con la variabile "readable")
{
inputString = buffer;
fragmentString(inputString, "|", data); //E' simile a un explode, l'ho fatta io, frammenta la stringa in un vettore di stringhe, spezzandola con la stringa (e quindi non un solo carattere) specificata, in questo caso "|" (una sola barra)
if(!(data[0].empty()) && !(data[1].empty()) && !(data[2].empty()) && !(data[3].empty()) && !(data[4].empty())) //Controllo per evitare SIGSEGV in caso di ricezione non completa o corretta della stringa
{
//Elaborazione dati...
}
}
/*SEND PHASE******************************/
if(!serverStatus.empty()) //Se c'è almeno uno snapshot da leggere
{
outputString.clear();
sStatus = serverStatus.begin(); //Prendo lo snapshot più recente
while() //Questo while elabora tutti i dati dello snapshot e li carica in una stringa con una struttura precisa separata da "|" (che verrà poi frammentata dal client usando la stringa "|"
{
//Elaborazione
}
}
memset(buffer, '\0', MAX_BUFFER);
if (outputString.size() < MAX_BUFFER - 100) //Giusto per assicurarmi che il buffer non vada in overflow
strcpy(buffer, outputString.c_str());
else
cout <<"String too big"<<endl;
status = client[id].socket.send(buffer, MAX_BUFFER); //Invio
}
}
if (status != Socket::Done && status != Socket::NotReady) //Se ci sono stati errori, tutti i cicli terminano e la funzione giunge alla fine, terminando il thread.
{
cout <<"Client disconnected: " <<id <<"." <<endl;
client[id].clean();
}
}
//CLIENT
//La struttura è molto simile:
void communicationThread()
{
Socket::Status status = Socket::Done;
string data[50];
int i = 0;
char buffer[MAX_BUFFER];
size_t received;
stringstream ss;
string outputString;
bool connected = true;
client.socket.setBlocking(true);
while(status == Socket::Done || status == Socket::NotReady)
{
/*SEND PHASE******************************/
// Loading outputString with data
outputString.clear();
//Elaborazione dati
outputString = ss.str();
//Sending
memset(buffer, '\0', MAX_BUFFER);
strcpy(buffer, outputString.c_str());
sleep(milliseconds(3)); //Ecco un errore che non capisco perchè avviene: se non metto uno sleep in questa funzione (nel client) il client inizia a laggare come non mai, probabilmente per una desincronizzazione delle funzioni, fino a dare un SIGSEGV
status = client.socket.send(buffer, MAX_BUFFER);
/*RECEIVE PHASE******************************/
if (status == Socket::Done)
{
//Receiving
memset(buffer, '\0', MAX_BUFFER);
i = 0;
status = client.socket.receive(buffer, MAX_BUFFER, received);
if (status == Socket::Done && buffer[0] != '\0' && ISReadable == false) //Se ho ricevuto senza errori
{
inputString = buffer;
ISReadable = true; //Il main può leggere ed elaborare la stringa
}
}
}
if (status != Socket::Done && status != Socket::NotReady) //Uguale al server
{
cout <<"Disconnected." <<endl;
connected = false; //Variabile per il client
}
}
Dal momento che era solo un prototipo mi perdono per non essermi preoccupato di rendere gli attributi privati, e di altri eventuali errori di stile classico. Se ne notate altri, sarebbe carino farmeli notare ora così posso scrivere del bel codice.
Ovviamente se capisco davvero bene come funziona l'i/o asincrono posso permettermi di fare anche più client per ogni thread, tipo 500 a thread, in modo da bilanciare bene il carico tra di essi e rendere il multithreading efficiente anche da parte della computazione.
Ah, il server deve essere strutturato per mantenere
tanti client, da 0 a... Finchè il pc li regge, anche migliaia. Non so se questa cosa comporta differenze nella mia funzione.
(Che, a proposito, poi diventerà un metodo della classe "Server"... Credo sia un metodo stantard nel C++)
Grazie dell'eventuale supporto <3