diff options
Diffstat (limited to 'src/netlem_ds.c')
-rw-r--r-- | src/netlem_ds.c | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/netlem_ds.c b/src/netlem_ds.c new file mode 100644 index 0000000..507b4fb --- /dev/null +++ b/src/netlem_ds.c @@ -0,0 +1,501 @@ +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include "SDL/SDL.h" +#include "SDL/SDL_net.h" +#include "SDL/SDL_thread.h" + +#include "game.h" +#include "netlem_ds.h" +#include "events.h" +#include "utils.h" + +//struct _timerArgs_t { int *numClients; client_t *clients[]; }; // Comprendre pq ça chie ça +struct _timerArgs_t { int *numClients; client_t **clients; }; + +game_t evilGlobalGameVariable = { gameNew, 0, 0, NULL }; +int end=0; + +void signals(int signum); +Uint32 timeSyncInsertNullEventsIfNeeded(Uint32 interval, void *args); +TCPsocket initServerSocket(int port, SDLNet_SocketSet *sockSetServer); +int acceptClient(TCPsocket sockServer, int *numThreads, client_t *clients[], SDL_Thread *threads[] ); +int serveClient(void *args); +int sendEvents(void *args); +int checkIfAllClientAre(enum clientState state, game_t *game); +int startGame(game_t *game); + +int main(int argc, char *argv[]) { + + int numThreads=0, result; + struct _timerArgs_t timerArgs; + + char logMsg[128]; + SDL_Thread *threads[MAX_THREADS]; + client_t *clients[MAX_THREADS]; + + TCPsocket sockServer; + SDLNet_SocketSet sockSetServer; + SDL_TimerID timerTimeSync; + + //TODO : dégager ce bidule temporaire + evilGlobalGameVariable.clients=clients; + + openLog(NULL); + + logs(LOG_INFO, "Server initialization in progress..."); + + result = SDL_Init(SDL_INIT_TIMER); + if ( result != 0 ) { + logs2(LOG_ERROR, "main(), SDL_Init()", SDL_GetError()); + return 1; + } + atexit(SDL_Quit); + signal(2,signals); + + + // Ecoute sur port principal pour recevoir les clients + sockServer=initServerSocket(DEFAULT_LISTEN_PORT, &sockSetServer); + if (sockServer == NULL ) { + return 2; + } + + // Mise en place du timer pour executer périodiquement la fonction server-side pour la synchro du temps + timerArgs.numClients=&numThreads; + timerArgs.clients=clients; + timerTimeSync = SDL_AddTimer(1000, timeSyncInsertNullEventsIfNeeded, &timerArgs); + if ( timerTimeSync == NULL ) { + logs2(LOG_ERROR, "main(), SDL_AddTimer()", SDL_GetError()); + return 3; + } + + logs(LOG_INFO, "Server ready to accept clients"); + // Acceptation des clients qui tentent de se connecter + while(!end) { + SDLNet_CheckSockets(sockSetServer, -1); // Appel bloquant jusqu'à activité + acceptClient(sockServer, &numThreads, clients, threads); + } + + logs(LOG_INFO, "Server is shuting down..."); + + // Libération de toutes les ressources + SDLNet_TCP_Close(sockServer); + SDLNet_FreeSocketSet(sockSetServer); + SDL_RemoveTimer(timerTimeSync); + + // Attente de terminaison des autres threads + logs(LOG_DEBUG, "Waiting all remaining threads\n"); + while(numThreads>=0) { + int status; + SDL_WaitThread(threads[numThreads-1], &status); + --numThreads; + sprintf(logMsg, "Thread %i terminated with code %i", numThreads,status); + logs(LOG_DEBUG, logMsg); + } + + + logs(LOG_INFO, "Server down"); + + closeLog(); + + return 0; +} + +void signals(int signum) { + static int force=0; + char buf[128]; + sprintf(buf, "Caught signal %i", signum); + logs(LOG_WARN, buf); + + if(!force) { + end=1; + force=1; + logs(LOG_WARN, "Trying to stop smoothly..."); + } else { + logs(LOG_WARN, "Emergency stop"); + exit(1); + } +} + +Uint32 timeSyncInsertNullEventsIfNeeded(Uint32 interval, void *args) { + const event_t evTimeSyncModel = {0,0,0,eTimeSync, 0,0, NULL,NULL}; + static int inProgress=0; + + game_t *game=NULL; + client_t *cli=NULL; + event_t *evTimeSync=NULL; + int i; + +// char buf[128]; + + struct _timerArgs_t *a=(struct _timerArgs_t *)args; + +// logs(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded() : begin"); + + if (inProgress != 0) { + logs(LOG_WARN, "timeSyncInsertNullEventsIfNeeded() takes too long !"); + return 200; + } + inProgress=1; + +// sprintf(buf, "*(a->numClients)==%i", *(a->numClients)); +// logs2(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded()", buf); + + //TODO : be _globally_ thread-safe for all the loop + for(i=0 ; i < *(a->numClients) ; ++i) { + cli=a->clients[i]; + game=cli->game; + +// sprintf(buf, "game->state==%i, SDL_GetTicks()==%i, cli->lastEventSendTime_ms==%i", game->state, SDL_GetTicks(), cli->lastEventSendTime_ms); +// logs2(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded()", buf); + + if ( game->state == gameRunning && + ( (SDL_GetTicks() - cli->lastEventSendTime_ms) > (0.9 * MAX_EVENT_INTERVAL_MS) ) + ) { + evTimeSync=malloc(sizeof(event_t)); + if (evTimeSync==NULL) { + logp(LOG_ERROR, "timeSyncInsertNullEventsIfNeeded(), malloc()"); + return 0; + } + memcpy(evTimeSync,&evTimeSyncModel,sizeof(event_t)); + eventListAdd((&cli->events), evTimeSync); + SDL_SemPost(cli->semEventAvailable); // FIXME Code retour + } + } + +// logs(LOG_DEBUG, "timeSyncInsertNullEventsIfNeeded() : end"); + + inProgress=0; + + return 1000; // TODO : optimiser la régularité avec la variable interval +} + +TCPsocket initServerSocket(int port, SDLNet_SocketSet *sockSetServer) { + int result; + TCPsocket sockServer; + IPaddress ip; + + // Crée un socket TCP en écoute sur le port passé en paramètre bindé en 0.0.0.0 + if(SDLNet_ResolveHost(&ip,NULL,port)==-1) { + logs2(LOG_ERROR, "initServerSocket(), SDLNet_ResolveHost()", SDLNet_GetError()); + return NULL; + } + + sockServer=SDLNet_TCP_Open(&ip); + if(!sockServer) { + logs2(LOG_ERROR, "initServerSocket(), SDLNet_TCP_Open()", SDLNet_GetError()); + return NULL; + } + + // Crée un socketSet qui permet essentiellement d'utiliser SDLNet_Check_Sockets() + if (sockSetServer != NULL) { + *sockSetServer = SDLNet_AllocSocketSet(1); + if ( *sockSetServer < 0 ) { + logs2(LOG_ERROR, "initServerSocket(), SDLNet_SocketSet()", SDLNet_GetError()); + return NULL; + } + + result = SDLNet_TCP_AddSocket(*sockSetServer,sockServer); + if ( result < 0 ) { + logs2(LOG_ERROR, "initServerSocket(), SDLNet_TCP_AddSocket()", SDLNet_GetError()); + return NULL; + } + } + + return sockServer; +} + +int acceptClient(TCPsocket sockServer, int *numThreads, client_t *clients[], SDL_Thread *threads[] ) { + //TODO : faire une vraie gestion d'un pool de threads et de clients + TCPsocket sockClient; + SDL_Thread *threadClient=NULL; + client_t *cli=NULL; + + sockClient=SDLNet_TCP_Accept(sockServer); // Appel non bloquant (contrairement à accept() en unix) + if(!sockClient) { + logs2(LOG_WARN, "acceptClient(), SDLNet_TCP_Accept()", SDLNet_GetError()); + return 1; + } + + if(*numThreads >= MAX_THREADS) { + logs2(LOG_WARN, "acceptClient()", "Too many threads"); + SDLNet_TCP_Close(sockClient); + return 2; + } + + //Préparation de la structure de données pour stocker l'état du nouveau client + cli=malloc(sizeof(client_t)); + if( cli==NULL ) { + logp(LOG_ERROR, "acceptClient()), malloc()"); + SDLNet_TCP_Close(sockClient); + return 3; + } + + cli->numClient=*numThreads; + cli->state=clientNew; + cli->sockClient = sockClient; + eventListInit(&cli->events); + cli->semEventAvailable = SDL_CreateSemaphore(0); //FIXME : test retour + cli->semGameStart = SDL_CreateSemaphore(0); + cli->lastEventSendTime_ms=0; + cli->game=NULL; + cli->lems=NULL; + + clients[(*numThreads)]=cli; + //TODO : Dégager ce truc temporaire + evilGlobalGameVariable.clientCount=(*numThreads)+1; + + //Préparation du thread correspondant à ce nouveau client (modèle 1 client => 1 thread) + threadClient = SDL_CreateThread(serveClient, (void *)cli); + if(!threadClient) { + logs2(LOG_ERROR, "acceptClient(), SDL_CreateThread()", SDL_GetError()); + SDLNet_TCP_Close(sockClient); + SDL_DestroySemaphore(cli->semEventAvailable); + SDL_DestroySemaphore(cli->semGameStart); + eventListFree(&(cli->events)); + free(cli); + return 5; + } + + //Inscrption dans le pool de clients et de threads + threads[(*numThreads)]=threadClient; + ++(*numThreads); + + + return 0; +} + +// Fonction exécutée par le thread associé à un client +int serveClient(void *args) { + int i, result; + char logMsg[128]; + const char *hostTmp; + char host[128]; + IPaddress *ipRemote=NULL; + SDL_Thread *threadSendEvents=NULL; + event_t e; + + client_t *otherClient=NULL; + client_t *c=(client_t *) args; + + + // Récupération d'infos à propos du client (au sens réseau) : nom d'hôte et port distant + ipRemote=SDLNet_TCP_GetPeerAddress(c->sockClient); + if(!ipRemote) { + logs2(LOG_ERROR, "serveClient(), SDLNet_TCP_GetPeerAddress", SDLNet_GetError()); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 1; + } + + hostTmp=SDLNet_ResolveIP(ipRemote); + if(!hostTmp) { + logs2(LOG_INFO, "serveClient(), SDLNet_ResolveIP", SDLNet_GetError()); + } else { + strcpy(host, hostTmp); //FIXME possible buffer overflow + // Add a default "(Unknown)" value) + } + + //TODO : faire ici toute la partie de l'automate côté serveur pour la phrase d'initialisation de la partie + c->game=&evilGlobalGameVariable; + + // On attends le signal du client qu'il envoi lorsqu'il a chargé les ressources nécessaires a la partie + sprintf(logMsg, "Waiting client %i (%s)", c->numClient, host); + logs(LOG_INFO, logMsg); + + //FIXME : utiliser reveiveEvent + result=SDLNet_TCP_Recv(c->sockClient,&e,sizeof(e)); + if(result<=0) { + logs2(LOG_ERROR, "serveClient(), SDLNet_TCP_Recv()", SDLNet_GetError()); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 3; + } + if ( e.type != eReady) { + logs(LOG_WARN, "serveClient(), first event received is not eReady"); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 4; + } + + // Création d'un nouveau thread par client pour l'envoi asynchrone des évènements + threadSendEvents = SDL_CreateThread(sendEvents, args); + if(!threadSendEvents) { + logs2(LOG_ERROR, "serveClient(), SDL_CreateThread()", SDL_GetError()); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 5; + } + + sprintf(logMsg, "Client %i (%s) ready", c->numClient, host); + logs(LOG_INFO, logMsg); + + // Si tous les (autres) clients de la partie sont prêts, on initie le début de la partie + c->state=clientReady; + if ( c->game->clientCount == 2 && checkIfAllClientAre(clientReady, c->game) == 0) { + result=startGame(c->game); + if (result!=0) { + logs(LOG_ERROR, "serveClient(), startGame() error"); + return 5; //FIXME : 5bis ! + } + } + + // Ce thread se bloque jusqu'au démarrage de la partie (débloquage immédiat si on est rentré dans le if précédent) + logs(LOG_INFO, "Waiting for game start"); + result=SDL_SemWait(c->semGameStart); + if (result!=0) { + logs2(LOG_ERROR, "serveClient(), SDL_SemWait()", SDL_GetError()); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 6; + } + logs(LOG_INFO, "Game Started !"); + + // Boucle principale de jeu pour chaque client du point de vue du serveur + while(c->game->state != gameEnded ) { + result=receiveEvent(c, &e); + if(result!=0) { + c->game->state = gameEnded; + continue; + } + + switch(e.type) { + case eLemAction: + for( i=0 ; i < (c->game->clientCount); ++i ) { + otherClient=c->game->clients[i]; + eventListAdd(&(otherClient->events), &e); + SDL_SemPost(otherClient->semEventAvailable); //FIXME code de retour + } + break; + default: + logs(LOG_ERROR, "serveClient(), Unknown event type"); + } + } + + // La partie est terminée, provoque et attends la terminaison du threadSendEvents + result=SDL_SemPost(c->semEventAvailable); + if(result!=0) { + logs(LOG_ERROR, "serveClient(), SDL_SemPost() error"); + SDLNet_TCP_Close(c->sockClient); + free(args); + return 7; + } + SDL_WaitThread(threadSendEvents, &result); + + logs2(LOG_INFO, "serveClient() ended for", (char *)host); + + SDLNet_TCP_Close(c->sockClient); + free(args); + + return 0; +} + +// Fonction principale du thread qui envoi sur le réseau les event_t qui sont en file pour chaque client +int sendEvents(void *args) { + char *buf=NULL/*, bufDbg[128]*/; + int i, result, eLen, msgLen; + client_t *c=(client_t *) args; + event_t *e; + + // S'autodétruit si la partie est terminée + // Note : peut être bloqué sur SemWait, donc faire un V après avoir changé le game state + while(c->game->state != gameEnded ) { + //Attends qu'un ou des events soient disponibles + result=SDL_SemWait(c->semEventAvailable); + if(result!=0) { + logs(LOG_ERROR, "sendEvents(), SDL_SemWait() error"); + return 1; + } + +// logs(LOG_DEBUG, "Event available !"); + + // Verrouile, enumère et sérialise dans buf tous les évènements de la file + eventListLock(&(c->events)); //FIXME : check return code + eLen=eventSerializedSize(); + msgLen=eLen * eventListItemCount((&c->events)); + buf=malloc(msgLen); + if ( buf==NULL ) { + logp(LOG_ERROR, "sendEvents(), malloc()"); + return 2; + } + + i=0; + while ((e=eventListPop(&(c->events))) != NULL) { + //Sending timestamp for time synchronization + e->serverTick=getGameCurrentTick(c->game->startTime_ms); + //Serialize messages and put them contiguous in buf + eventSerialize(e, buf+eLen*i++); + free(e); + } + eventListUnlock(&(c->events)); //FIXME : check return code + + // Envoi ces évènements sérialisés sur le réseau + if (msgLen>0) { + result=SDLNet_TCP_Send(c->sockClient,buf,msgLen); + if(result<msgLen) { + logs2(LOG_WARN, "sendEvents(), SDLNet_TCP_Send() error", SDLNet_GetError()); + return 3; + } + } + } + + return 0; +} + +// Retourne 0 si tous les clients d'une partie donnée sont dan sun état donné (1 sinon) +inline int checkIfAllClientAre(enum clientState state, game_t *game) { + int i; + for(i=0 ; i < (game->clientCount) ; ++i ) { + if(game->clients[i]->state != state) { return 1; } + } + return 0; +} + +int startGame(game_t *game) { + int i, result; + client_t *cli; + + char buf[128]; + + // Date de début dans le futur pour laisser du temps pour converger + // à l'algorithme de synchronisation du temps + game->startTime_ms=SDL_GetTicks()+TIME_FOR_TIMESYNC_MS; + + sprintf(buf, "game->startTime_ms==%i", game->startTime_ms); + logs(LOG_DEBUG, buf); + + game->state = gameRunning; + + // On débloque tous les threads des clients qui sont en attente + for(i=0 ; i < (game->clientCount) ; ++i) { + cli=game->clients[i]; + result=SDL_SemPost(cli->semGameStart); + if (result!=0) { + logs2(LOG_ERROR, "startGame(), SDL_SemPost()", SDL_GetError()); + return 1; + } + } + return 0; +} + +/* +Machine a états complète à implémenter +on lance le serveur, il se met en écoute et lance un thread / timer qui compte le temps (globalServerTick) +un client demande une partie réseau, se connecte au serveur, donne un username +le serveur le met dans un pool de client en attente et lui envoi la liste des clients en attente +un second client demande une partie réseau, idem +la liste des clients en attente est renvoyée au premier client (enfait à tous les clients) +le client1 choisi le client2 en coequipier et envoi cette info au serveur +le serveur transmet au client2 qui affiche la proposition +le client2 accepte et renvoi cette acceptation au serveur (s'il refuse rien n'est transmis) +le serveur voit que les 2 clients sont ok pour jouer ensemble. (si le client1 ne s'est pas barré ou a commencé ailleurs). Le serveur envoi un paquet à chacun : une demande de paramètres de jeu au premier (car il a initié) et une attente pour l'autre. +Le client1 modifier des paramètres de jeu, les transmet au serveur qui les forwarde au client2 pour acceptation. +Le client2 accepte les paramètres et envoi un paquet pour le ire au serveur. +Le serveur envoi un paquet à chacun pour dire de charger la partie. +Quand un client est (presque) prêt, il envoi un premier event NULL. +Quand le serveur a un event null de chaque joueur, il lance la partie en ajoutant la partie dans la liste des parties actives, en positionnant la variable tickGameStart à la valeur ( globalServerTick + TIME_FOR_TIMESYNC_MS ). +Les envois d'évèments commencent donc (un timer réveille une procédure qui checke la lastSentEventTick, si trop vieux, envoi d'un event NULL pour la synchro du temps) +Le reste de la logique est déclanhée sur la réception d'évènement des clients (remplissage de liste d'évènement, tri et renvoi à tous...) +*/ + |