#include #include "SDL/SDL.h" #include "SDL/SDL_net.h" #include "SDL/SDL_thread.h" #include "game.h" #include "netgame.h" #include "utils.h" #include "timing.h" // Application version number and some constants #include "netlem_ds.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 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 2*1000-interval; // == 1000 - (interval-1000) } 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 == NULL ) { 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(resultclientCount) ; ++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...) */