ref: 09a8d8c1dddb130846b931acbe22ffe036882359
dir: /srv5.c/
/* * glendy (unix) server, 5th revision * leaks memory, often, quick and well. */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <unistd.h> #include <time.h> #include <pthread.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <ctype.h> #include <signal.h> #include "unix.h" #include "engine.h" #include "util.h" #include "srv.h" int listenfd; int port = 1769; int debug = 1; char syncmsg[8]; int sockfd[2]; int gcount = 0; int ccount = 0; int id = 0; // extern char *malloc_options; // malloc_options = "JX" pthread_mutex_t game_lock; List *games; List *threads; Quene clients; static void error(const char *msg) { perror(msg); pthread_exit(NULL); } static void cleanup(int gid) { Game *g; dprint("cleanup(%d)\n", gid); g = (Game*)lookup(games, gid); if(g == nil) return; if(g->state != Finished) { g->state = Finished; shutdown(g->sockfd[0], SHUT_RDWR); shutdown(g->sockfd[1], SHUT_RDWR); close(g->sockfd[0]); close(g->sockfd[1]); } else { /* we can't delete whole list because we need to update gid for rest of games too! */ free(g->clients[0]->nick); free(g->clients[0]); free(g->clients[1]->nick); free(g->clients[1]); free(g); g = nil; } } static void printclients(char *fmt, ...) { /* it seems arg gets changed during the first call to vprint, thus we need two */ va_list arg, arg2; va_start(arg, fmt); va_start(arg2, fmt); vfprint(sockfd[0], fmt, arg); vfprint(sockfd[1], fmt, arg2); va_end(arg); va_end(arg2); } int setuplistener(int portno) { int sockfd, option = 1; struct sockaddr_in serv_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening listener socket."); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) error("ERROR binding listener socket."); dprint("listener on\n"); return sockfd; } void drawlevel(void) { /* prints first row, this assumes SzX = SzY, is there a better way? */ print("T%2d|", turn); for(int i = 0 ; i < SzX ; i++) print("%2d |", i); dprint(" G%2d\n", id); for(int x = 0; x < SzX; x++) { for(int i = 0 ; i < SzY+1 ; i++) print("----"); print(x % 2 ? "\\" : "/"); print("\n"); /* show column number and have a zig-zag effect */ print("%2d %s|", x, x % 2 ? " " : ""); for(int y = 0; y < SzY; y++) { /* it's [y][x], not [x][y] */ switch(grid[y][x]) { case Wall: print(" * |"); break; case Glenda: print(" g |"); break; default: print(" |"); break; } } print("\n"); } } void sendlevel(void) { if(state == Start) { printclients("INIT\n"); for(int x = 0; x < SzX; x++) { for(int y = 0; y < SzY; y++) { switch(grid[x][y]) { case Wall: printclients("w %d %d\n", x, y); break; case Glenda: printclients("g %d %d\n", x, y); break; } } } printclients("SENT\n"); } else if(state == Playing) printclients("SYNC %d %s\n", turn, syncmsg); else { printclients("SYNC %d %s\n", turn, syncmsg); if(state == Won) { dprint("hah, trapper won\n"); fprint(sockfd[0], "WON\n"); fprint(sockfd[1], "LOST\n"); } else if(state == Lost) { dprint("welp, trapper lost\n"); fprint(sockfd[0], "LOST\n"); fprint(sockfd[1], "WON\n"); } cleanup(id); return; } dprint("Game %d, Turn is %d, min = %d\n", id, turn, findmin(findglenda())); fprint(playersock, "TURN\n"); fprint(sockfd[!(turn % 2)], "WAIT\n"); } /* p x y */ void proc_put(char *s) { char *xpos, *ypos; unsigned int x, y, r; xpos = strtok(s, " "); ypos = strtok(nil, " "); if(xpos == nil || ypos == nil) { fprint(playersock, "ERR INVALIDINPUT proc_put():" "not enough arguments or malformed string\n"); return; } if(!isnum(xpos, strlen(xpos)) || !isnum(ypos, strlen(ypos))) { fprint(playersock, "ERR invalidinput proc_put():" "expected string in %s and %s\n", xpos, ypos); return; } /* engine assumes it's XY, protocol assumes it's YX */ x = atoi(xpos); y = atoi(ypos); dprint("put %d %d\n", x, y); if(x >= SzX || x < 0 || y >= SzY || y < 0) { fprint(playersock, "ERR invalidinput proc_put(): %d %d\n", x, y); return; } r = doput(Pt(x, y)); if(r == Wall) fprint(playersock, "ERR wall in %d %d\n", x, y); else if(r == Glenda) fprint(playersock, "ERR glenda in %d %d\n", x, y); else { sprint(syncmsg, "%u %u", x, y); syncmsg[7] = '\0'; dprint("syncmsg = %s\n", syncmsg); } } /* m x y */ void proc_move(char *s) { int d; Point p; p = findglenda(); d = strtodir(s); if(d == Err) fprint(playersock, "ERR invalidinput proc_move(): %s\n", s); else if(domove(d) == Wall) fprint(playersock, "ERR glenda %s %d %d\n", s, p.x, p.y); else { strncpy(syncmsg, s, 7); /* better be safe than sorry */ syncmsg[7] = '\0'; dprint("syncmsg = %s\n", syncmsg); } } /* * handle input, which is in the form of: * trapper> p xx yy * puts a wall in xx yy * glenda> m {NE, E, SE, SW, W, NW} * moves the bunny * > q * quits the game */ int proc(int player, char *s) { int oturn; /* early return paths */ if(*s == '\0' || *s == 'q') { fprint(sockfd[player], "DIE disconnected\n"); fprint(sockfd[!player], "DIE other client have been disconnected\n"); /* mmhm... what happens if we close a fd we are reading from? */ cleanup(id); return Err; } else if(turn % 2 != player) { fprint(sockfd[player], "ERR WAIT\n"); return Ok; } oturn = turn; /* s+2 skips command and first space after it */ switch(*s) { case 'p': if(turn % 2 == 0) proc_put(s+2); else if(turn % 2 == 1) fprint(playersock, "ERR you can't put walls\n"); break; case 'm': if(turn % 2 == 0) fprint(playersock, "you can't move!\n"); else if(turn % 2 == 1) proc_move(s+2); break; default: fprint(playersock, "ERR proc() unkown command %c\n", *s); } /* only print the map if turn have changed */ if(turn != oturn) { if(debug) drawlevel(); sendlevel(); } return Ok; } static char* rawinput(int fd) { char *s, c; int n = 0; /* sang bozorg */ s = (char*)emalloc(INPUTSIZE); memset(s, 0, INPUTSIZE); /* we could use local variables, but that not worth the trouble */ while((c = read(fd, s+n, 1) == 1) && n < INPUTSIZE) { /* silently drop CR from CRLF */ if(s[n] == '\r') s[n] = '\0'; if(s[n] == '\n' || s[n] == '\0') { s[n] = '\0'; break; } n++; } if(strcmp(s, "")) dprint("rawinput(%d): got input: 0x%x, %s\n", fd, *s, s); return s; } static char* input(int gid, int player) { char *s; Game *g; g = (Game*)lookup(games, gid); s = rawinput(g->sockfd[player]); g->clients[player]->lastseen = time(nil); return s; } /* * most of game engine's works on global variables * set the global variables with one game from the array */ static void loadgame(int gid) { Game *g; // if(n > sizeof(games) / sizeof(Game)) // sysfatal("loadgame(%d): invalid game", n); dprint("loadgame(%d)\n", gid); g = (Game*)lookup(games, gid); id = g->id; difficulty = g->difficulty; state = g->state; turn = g->turn; strncpy(syncmsg, g->syncmsg, 8); sockfd[0] = g->sockfd[0]; sockfd[1] = g->sockfd[1]; memcpy(grid, g->grid, sizeof(grid)); } static void setgame(int gid) { Game *g; // if(n > lllen(games)) // sysfatal("setgame(%d): invalid game", n); dprint("loadgame(%d)\n", gid); g = (Game*)lookup(games, gid); /* id got to change when we set game */ id = gid; g->id = gid; g->difficulty = difficulty; g->state = state; g->turn = turn; g->sockfd[0] = sockfd[0]; g->sockfd[1] = sockfd[1]; memcpy(g->grid, grid, sizeof(grid)); } /* player is either 0 or 1, trapper or glenda */ static void clienthandler(void *data) { int gid, player; char *s; gid = ((int*)data)[0]; player = ((int*)data)[1]; dprint("clienthandler({%d, %d})\n", gid, player); for(;;) { /* most of time is spent here */ s = input(gid, player); if(!strcmp(s, "")) break; pthread_mutex_lock(&game_lock); loadgame(gid); proc(player, s); setgame(gid); pthread_mutex_unlock(&game_lock); free(s); } cleanup(gid); free(data); } static void play(Client *c1, Client *c2) { int res[2]; int *tdata1, *tdata2; Game *g; tdata1 = (int*)emalloc(2 * sizeof(int)); tdata2 = (int*)emalloc(2 * sizeof(int)); pthread_mutex_lock(&game_lock); gcount++; sockfd[0] = c1->fd; sockfd[1] = c2->fd; c1->state = Init; c2->state = Init; g = (Game*)emalloc(sizeof(Game)); llappend(games, g); initlevel(); setgame(gcount); if(debug) drawlevel(); fprint(g->sockfd[0], "CONN %d %s\n", 0, c2->nick); fprint(g->sockfd[1], "CONN %d %s\n", 1, c1->nick); sendlevel(); tdata1[0] = gcount; tdata1[1] = 0; tdata2[0] = gcount; tdata2[1] = 1; c1->thread = (pthread_t*)emalloc(sizeof(pthread_t)); c2->thread = (pthread_t*)emalloc(sizeof(pthread_t)); g->clients[0] = c1; g->clients[1] = c2; // llappend(threads, c1->thread); // llappend(threads, c2->thread); pthread_mutex_unlock(&game_lock); res[0] = pthread_create(c1->thread, nil, (void*)clienthandler, (void*)tdata1); res[1] = pthread_create(c2->thread, nil, (void*)clienthandler, (void*)tdata2); dprint("play(): tdata[0] {%d, %d}\n", tdata1[0], tdata1[1]); dprint("play(): tdata[1] {%d, %d}\n", tdata2[0], tdata2[1]); if(res[0] || res[1]) sysfatal("pthread_create() failed: %d\n", res[0] ? res[0] : res[1]); dprint("play(): threads for game %d are created\n", gcount); } char* parsenick(int fd, char *nick) { char *s; int len = strlen(nick); if(len > 8) { fprint(fd, "DIE nick too long: %d\n", len); goto die; } /* this is wrong, but we run strdup anyway */ if(!strcmp(nick, "@")) nick = "Guest"; for(int i = 0 ; len > i ; i++) { if(!isalnum(nick[i])) { fprint(fd, "DIE nicks must be only alpha numeric strings\n"); goto die; } } s = strdup(nick); return s; die: close(fd); return nil; } int parsegame(int fd, char *game) { int g; g = atoi(game); if(g == 0) return g; fprint(fd, "DIE invalid game: %d", g); close(fd); return -1; } int parseside(int fd, char *side) { int s; s = atoi(side); if(s == PGlenda || s == PTrapper || s == PRandom) return s; fprint(fd, "DIE invalid side: %d", s); close(fd); return -1; } int parseopts(int fd, char *opts) { return atoi(opts); } Client* newclient(char *in, int fd) { Client *c; char *nick, *side, *game, *opts; nick = strtok(in, " "); game = strtok(nil, " "); side = strtok(nil, " "); opts = strtok(nil, " "); dprint("newclient(%d): nick: %s, side: %s, game: %s, opts: %s\n", fd, nick, side, game, opts); if(nick == nil || side == nil || game == nil) return nil; c = (Client*)emalloc(sizeof(Client)); c->fd = fd; c->nick = parsenick(fd, nick); c->game = parsegame(fd, game); c->side = parseside(fd, side); c->opts = parseopts(fd, opts); c->firstseen = time(nil); c->lastseen = c->firstseen; c->state = Connect; if(c->nick == nil || c->game == -1 || c->side == -1) { free(c); close(fd); return nil; } ccount++; return c; } void checkquene(void) { Client *c; long t; t = time(nil); dprint("checkquene(%d)\n", t); if(clients.len == 0) return; for(List *l = clients.head ; l != nil ; l = l->next) { c = (Client*)l->data; if(c->lastseen + TIMEOUT < t) { dprint("checkquene(): found match t: %d lastseen: %d nick: %s fd: %d\n", t, c->lastseen, c->nick, c->fd); free(c->nick); // free(c->thread); qdel(&clients, l); free(c); } } } int makematch(Client *c) { Client *head; dprint("makematch(%p)\n", c); pthread_mutex_lock(&game_lock); checkquene(); if(clients.head == nil) { dprint("clients.head == nil\n"); clients.head = llnew(); clients.tail = clients.head; } head = (Client*)clients.head->data; if(clients.len == 0 || c->side == head->side) { /* head->side can never be PRandom anyway */ if(c->side == PRandom) c->side = nrand(1) ? PTrapper : PGlenda; qadd(&clients, c); pthread_mutex_unlock(&game_lock); fprint(c->fd, "WAIT\n"); return 0; } else { if(c->side == PRandom) c->side = (head->side == PGlenda) ? PTrapper : PGlenda; qnext(&clients); // if(!isonline(head)) // goto loop; pthread_mutex_unlock(&game_lock); if(c->side == PTrapper) play(c, head); else play(head, c); return 1; } } void keepalive(Client *c) { char *s; while(c->state == Connect) { fprint(c->fd, "UGUD\n"); s = rawinput(c->fd); if(!strcmp(s, "y")) c->lastseen = time(nil); else { dprint("keepalive(): rude client %d sent me %s!!11\n", c->fd, s); if(strcmp(s, "") != 0) fprint(c->fd, "DIE rude\n"); c->lastseen = 0; break; } free(s); sleep(TIMEOUT); } } void registerclient(void *clientfd) { char c, *s; Client *cl; int n = 0; int fd = *(int*)clientfd; dprint("registerclient(%d)\n", fd); /* sang bozorg */ s = (char*)emalloc(INPUTSIZE); memset(s, 0, INPUTSIZE); /* we could use local variables, but that not worth the trouble */ while((c = read(fd, s+n, 1) == 1) && n < INPUTSIZE) { /* silently drop CR from CRLF */ if(s[n] == '\r') s[n] = '\0'; else if(s[n] == '\n' || s[n] == '\0') { s[n] = '\0'; break; } n++; } if(!strcmp(s, "")) { dprint("registerclient(%d): got empty string\n", fd); fprint(fd, "DIE empty string\n"); close(fd); goto die; } dprint("registerclient(%d): got input: %x, %s\n", fd, *s, s); cl = newclient(s, fd); if(cl != nil) { if(!makematch(cl)) keepalive(cl); } die: free(s); } void srv(int listenfd) { int *clientfd; socklen_t clilen; struct sockaddr_in clientaddr; pthread_t t; for(;;) { listen(listenfd, 64); memset(&clientaddr, 0, sizeof(clientaddr)); clilen = sizeof(clientaddr); clientfd = (int*)emalloc(sizeof(int)); *clientfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen); if(*clientfd < 0) error("srv(): failed to accept connection"); dprint("game %d: srv(): client %d connected, fd: %d\n", gcount, ccount, *clientfd); pthread_create(&t, NULL, (void*)registerclient, (void*)clientfd); } } int main(int argc, char **argv) { /* it might not be a real human */ ptype[0] = Human; ptype[1] = Human; listenfd = setuplistener(port); pthread_mutex_init(&game_lock, NULL); signal(SIGPIPE, SIG_IGN); /* OpenBSD ignores this */ srand(time(nil)); games = llnew(); clients.len = 0; clients.head = llnew(); clients.tail = clients.head; threads = llnew(); srv(listenfd); close(listenfd); pthread_mutex_destroy(&game_lock); pthread_exit(NULL); return 0; }