From ae96cfa10904f1d69cd5c4189dc140fc563f960c Mon Sep 17 00:00:00 2001 From: rodri Date: Fri, 15 Sep 2023 08:57:29 +0000 Subject: new server architecture. --- btsd.c | 662 +++++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 502 insertions(+), 160 deletions(-) (limited to 'btsd.c') diff --git a/btsd.c b/btsd.c index 1177c0c..54b4fd6 100644 --- a/btsd.c +++ b/btsd.c @@ -11,26 +11,136 @@ int debug; Channel *playerq; +Channel *msgq; +Channel *mmctl; /* matchmaker's */ +Match theater; +RWLock theaterlk; +Player * +newplayer(int fd) +{ + Player *p; + + p = emalloc(sizeof *p); + p->name[0] = 0; + p->state = Waiting0; + p->battle = nil; + p->nci = getnetconninfo(nil, fd); + p->io.fd = fd; + p->io.in = chancreate(sizeof(char*), 8); + p->io.out = chancreate(sizeof(char*), 8); + p->ctl = chancreate(sizeof(char*), 1); + p->io.ctl = p->ctl; + return p; +} + void freeplayer(Player *p) { - close(p->sfd); - close(p->fd); + chanfree(p->io.out); + chanfree(p->io.in); + chanfree(p->ctl); + close(p->io.fd); + freenetconninfo(p->nci); free(p); } -int -isconnected(Player *p) +Msg * +newmsg(Player *p, char *s) { - char buf[8]; - int n; + Msg *m; + + m = emalloc(sizeof *m); + m->from = p; + m->body = s; /* must be malloc'ed */ + return m; +} + +void +freemsg(Msg *m) +{ + free(m->body); + free(m); +} + +Match * +newmatch(Player *p0, Player *p1) +{ + Match *m; + + m = emalloc(sizeof *m); + m->id = p0->io.fd + p1->io.fd; + m->data = chancreate(sizeof(Msg*), 8); + m->ctl = chancreate(sizeof(Msg*), 1); + m->pl[0] = p0; + m->pl[1] = p1; + return m; +} + +void +addmatch(Match *m) +{ + wlock(&theaterlk); + m->next = &theater; + m->prev = theater.prev; + theater.prev->next = m; + theater.prev = m; + wunlock(&theaterlk); +} + +Match * +getmatch(int id) +{ + Match *m; + + rlock(&theaterlk); + for(m = theater.next; m != &theater; m = m->next) + if(m->id == id){ + runlock(&theaterlk); + return m; + } + runlock(&theaterlk); + return nil; +} + +void +rmmatch(Match *m) +{ + wlock(&theaterlk); + m->prev->next = m->next; + m->next->prev = m->prev; + m->prev = nil; + m->next = nil; + wunlock(&theaterlk); +} + +void +freematch(Match *m) +{ + chanfree(m->data); + chanfree(m->ctl); + free(m); +} + +void +takeseat(Stands *s, Player *p) +{ + if(++s->nused > s->cap){ + s->cap = s->nused; + s->seats = erealloc(s->seats, s->nused * sizeof p); + } + s->seats[s->nused-1] = p; +} + +void +leaveseat(Stands *s, Player *p) +{ + int i; - n = pread(p->sfd, buf, sizeof buf, 0); - if(n < 0 || strncmp(buf, "Close", 5) == 0) - return 0; - return 1; + for(i = 0; i < s->nused; i++) + if(s->seats[i] == p) + memmove(&s->seats[i], &s->seats[i+1], --s->nused * sizeof p); } void @@ -50,198 +160,436 @@ netrecvthread(void *arg) buf[tot] = 0; while((e = strchr(buf, '\n')) != nil){ *e++ = 0; - chanprint(cp->c, "%s", buf); + chanprint(cp->in, "%s", buf); tot -= e-buf; memmove(buf, e, tot); } if(tot >= sizeof(buf)-1) tot = 0; } + closeioproc(io); + sendp(cp->ctl, nil); + threadexits(nil); +} + +void +netsendthread(void *arg) +{ + Chanpipe *cp; + char *s; + + cp = arg; + + while((s = recvp(cp->out)) != nil){ + if(write(cp->fd, s, strlen(s)) != strlen(s)) + sendp(cp->ctl, nil); + else if(debug) + fprint(2, "[%d] sent '%s'\n", getpid(), s); + free(s); + } + sendp(cp->ctl, nil); + threadexits(nil); +} + +void +playerproc(void *arg) +{ + Player *my; + char *s, *cmd[2]; + int nc; + + my = arg; + + threadsetname("player %s", my->nci->raddr); + + threadsetgrp(my->io.fd); + threadcreate(netrecvthread, &my->io, mainstacksize); + threadcreate(netsendthread, &my->io, mainstacksize); + + chanprint(my->io.out, "id\n"); + + enum { NETIN, CTL, NONE }; + Alt a[] = { + [NETIN] {my->io.in, &s, CHANRCV}, + [CTL] {my->ctl, &s, CHANRCV}, + [NONE] {nil, nil, CHANEND} + }; + for(;;) + switch(alt(a)){ + case NETIN: + if(debug) + fprint(2, "[%d] rcvd '%s'\n", getpid(), s); + if(my->name[0] == 0){ + nc = tokenize(s, cmd, nelem(cmd)); + if(nc == 2 && strcmp(cmd[0], "id") == 0 && strlen(cmd[1]) > 0) + snprint(my->name, sizeof my->name, "%s", cmd[1]); + else + chanprint(my->io.out, "id\n"); + free(s); + }else + sendp(msgq, newmsg(my, s)); + break; + case CTL: + if(s == nil) /* cable cut */ + goto End; + free(s); + break; + } +End: if(debug) fprint(2, "[%d] lost connection\n", getpid()); - closeioproc(io); - chanclose(cp->c); + sendp(msgq, newmsg(my, nil)); + threadkillgrp(threadgetgrp()); threadexits(nil); } +void +operator(void *) +{ + Msg *msg; + Match *m; + char *cmd[2]; + int nc, mid; + + threadsetname("operator"); + + while((msg = recvp(msgq)) != nil){ + if(debug) + fprint(2, "operator got '%s' from p(fd=%d)\n", msg->body, msg->from->io.fd); + + if(msg->body == nil){ /* player left */ + if(msg->from->state != Waiting0) + sendp(mmctl, newmsg(msg->from, estrdup("player left"))); + else + freeplayer(msg->from); + }else{ + switch(msg->from->state){ + case Waiting0: + nc = tokenize(msg->body, cmd, nelem(cmd)); + if(nc == 1 && strcmp(cmd[0], "play") == 0) + sendp(playerq, msg->from); + else if(nc == 1 && strcmp(cmd[0], "watch") == 0){ + if(theater.next == &theater) + chanprint(msg->from->io.out, "no matches\n"); + else for(m = theater.next; m != &theater; m = m->next) + chanprint(msg->from->io.out, "%d %s vs. %s\n", m->id, m->pl[0]->name, m->pl[1]->name); + }else if(nc == 2 && strcmp(cmd[0], "watch") == 0){ + mid = strtoul(cmd[1], nil, 10); + m = getmatch(mid); + if(m == nil) + chanprint(msg->from->io.out, "no such match\n"); + else + sendp(m->ctl, newmsg(msg->from, estrdup("take seat"))); + } + break; + case Watching: + nc = tokenize(msg->body, cmd, nelem(cmd)); + if(nc == 1 && strcmp(cmd[0], "leave") == 0) + sendp(msg->from->battle->ctl, newmsg(msg->from, estrdup("leave seat"))); + break; + default: + if(msg->from->battle != nil) + sendp(msg->from->battle->data, newmsg(msg->from, estrdup(msg->body))); + } + } + freemsg(msg); + } + threadexitsall("operator was KIA"); +} + void battleproc(void *arg) { - NetConnInfo *nci[2]; + Msg *msg; Match *m; Player *p, *op; - Chanpipe cp[2]; - Alt a[3]; - int i; + Stands stands; + char *cmd[2]; uint n0; - char *s; + int nc; Point2 cell; char *coords[5]; - int j, orient; + int i, orient; m = arg; - s = nil; - - nci[0] = getnetconninfo(nil, m->pl[0]->fd); - nci[1] = getnetconninfo(nil, m->pl[1]->fd); - if(nci[0] == nil || nci[1] == nil) - sysfatal("getnetconninfo: %r"); - threadsetname("battleproc %s ↔ %s", nci[0]->raddr, nci[1]->raddr); - freenetconninfo(nci[0]); - freenetconninfo(nci[1]); - - cp[0].c = chancreate(sizeof(char*), 1); - cp[0].fd = m->pl[0]->fd; - cp[1].c = chancreate(sizeof(char*), 1); - cp[1].fd = m->pl[1]->fd; - - a[0].c = cp[0].c; a[0].v = &s; a[0].op = CHANRCV; - a[1].c = cp[1].c; a[1].v = &s; a[1].op = CHANRCV; - a[2].op = CHANEND; - - threadsetgrp(cp[0].fd); - threadcreate(netrecvthread, &cp[0], mainstacksize); - threadcreate(netrecvthread, &cp[1], mainstacksize); - - for(i = 0; i < nelem(m->pl); i++) - if(!isconnected(m->pl[i])){ - sendp(playerq, m->pl[i^1]); - freeplayer(m->pl[i]); - goto Finish; - } + memset(&stands, 0, sizeof stands); + + threadsetname("battleproc [%d] %s ↔ %s", m->id, m->pl[0]->nci->raddr, m->pl[1]->nci->raddr); + + chanprint(m->pl[0]->io.out, "oid %s\n", m->pl[1]->name); + chanprint(m->pl[1]->io.out, "oid %s\n", m->pl[0]->name); + chanprint(m->pl[0]->io.out, "layout\n"); + chanprint(m->pl[1]->io.out, "layout\n"); + m->pl[0]->state = Outlaying; + m->pl[1]->state = Outlaying; + + enum { DATA, CTL, NONE }; + Alt a [] = { + [DATA] {m->data, &msg, CHANRCV}, + [CTL] {m->ctl, &msg, CHANRCV}, + [NONE] {nil, nil, CHANEND} + }; + for(;;){ + switch(alt(a)){ + case DATA: + if(debug) fprint(2, "[%d] battleproc rcvd '%s' from p(fd=%d) on data\n", getpid(), msg->body, msg->from->io.fd); - write(m->pl[0]->fd, "id\n", 3); - write(m->pl[1]->fd, "id\n", 3); + p = msg->from; + op = p == m->pl[0]? m->pl[1]: m->pl[0]; - while((i = alt(a)) >= 0){ - p = m->pl[i]; - op = m->pl[i^1]; + nc = tokenize(msg->body, cmd, nelem(cmd)); - if(a[i].err != nil){ - if(debug) - fprint(2, "[%d] alt: %s\n", getpid(), a[i].err); - write(op->fd, "win\n", 4); - sendp(playerq, op); - freeplayer(p); - break; - } - if(debug) - fprint(2, "[%d] said '%s'\n", i, s); - - switch(p->state){ - case Waiting0: - if(strncmp(s, "id", 2) == 0){ - snprint(p->name, sizeof p->name, "%s", strlen(s) > 3? s+3: "???"); - write(p->fd, "layout\n", 7); - p->state = Outlaying; - if(op->state == Outlaying){ - fprint(p->fd, "oid %s\n", op->name); - fprint(op->fd, "oid %s\n", p->name); - } - } - break; - case Outlaying: - if(strncmp(s, "layout", 6) == 0) - if(gettokens(s+7, coords, nelem(coords), ",") == nelem(coords)){ - if(debug) - fprint(2, "rcvd layout from %d\n", i); - for(j = 0; j < nelem(coords); j++){ - cell = coords2cell(coords[j]); - orient = coords[j][strlen(coords[j])-1] == 'h'? OH: OV; - /* TODO keep track of the ships and report back on the first shot and when sunk */ - settiles(p, cell, orient, shiplen(j), Tship); + switch(p->state){ + case Outlaying: + if(nc == 2 && strcmp(cmd[0], "layout") == 0) + if(gettokens(cmd[1], coords, nelem(coords), ",") == nelem(coords)){ + if(debug) + fprint(2, "rcvd layout from %s @ %s\n", p->name, p->nci->raddr); + for(i = 0; i < nelem(coords); i++){ + cell = coords2cell(coords[i]); + orient = coords[i][strlen(coords[i])-1] == 'h'? OH: OV; + settiles(p, cell, orient, shiplen(i), Tship); + } + p->state = Waiting; + if(op->state == Waiting){ + if(debug){ + fprint(2, "%s's map:\n", p->name); + fprintmap(2, p); + fprint(2, "%s's map:\n", op->name); + fprintmap(2, op); + } + n0 = truerand(); + if(debug) + fprint(2, "let the game begin: %s plays, %s waits\n", m->pl[n0%2]->name, m->pl[(n0+1)%2]->name); + chanprint(m->pl[n0%2]->io.out, "play\n"); + m->pl[n0%2]->state = Playing; + chanprint(m->pl[(n0+1)%2]->io.out, "wait\n"); + } } - p->state = Waiting; - if(op->state == Waiting){ - if(debug){ - fprint(2, "map%d:\n", i); - fprintmap(2, p); - fprint(2, "map%d:\n", i^1); - fprintmap(2, op); + break; + case Playing: + if(nc == 2 && strcmp(cmd[0], "shoot") == 0){ + cell = coords2cell(cmd[1]); + switch(gettile(op, cell)){ + case Tship: + settile(op, cell, Thit); + chanprint(p->io.out, "hit\n"); + chanprint(op->io.out, "hit %s\n", cell2coords(cell)); + if(countshipcells(op) < (debug? 17: 1)){ + chanprint(p->io.out, "win\n"); + chanprint(op->io.out, "lose\n"); + p->state = Waiting0; + p->battle = nil; + op->state = Waiting0; + op->battle = nil; + freemsg(msg); + goto Finish; } - n0 = truerand(); - if(debug) - fprint(2, "let the game begin: %d plays, %d waits\n", n0%2, (n0+1)%2); - write(m->pl[n0%2]->fd, "play\n", 5); - m->pl[n0%2]->state = Playing; - write(m->pl[(n0+1)%2]->fd, "wait\n", 5); + goto Swapturn; + case Twater: + settile(op, cell, Tmiss); + chanprint(p->io.out, "miss\n"); + chanprint(op->io.out, "miss %s\n", cell2coords(cell)); +Swapturn: + chanprint(p->io.out, "wait\n"); + chanprint(op->io.out, "play\n"); + p->state = Waiting; + op->state = Playing; + break; } + if(debug) + fprint(2, "%s plays, %s waits\n", op->name, p->name); } + break; + } + + freemsg(msg); break; - case Playing: - if(strncmp(s, "shoot", 5) == 0){ - cell = coords2cell(s+6); - switch(gettile(op, cell)){ - case Tship: - settile(op, cell, Thit); - write(p->fd, "hit\n", 4); - fprint(op->fd, "hit %s\n", cell2coords(cell)); - if((debug && countshipcells(op) < 17) || (!debug && countshipcells(op) < 1)){ - write(p->fd, "win\n", 4); - write(op->fd, "lose\n", 5); - sendp(playerq, p); - sendp(playerq, op); - goto Finish; - } - goto Swapturn; - case Twater: - settile(op, cell, Tmiss); - write(p->fd, "miss\n", 5); - fprint(op->fd, "miss %s\n", cell2coords(cell)); -Swapturn: - write(p->fd, "wait\n", 5); - write(op->fd, "play\n", 5); - p->state = Waiting; - op->state = Playing; - break; + case CTL: + if(debug) fprint(2, "[%d] battleproc rcvd '%s' from p(fd=%d) on ctl\n", getpid(), msg->body, msg->from->io.fd); + + p = msg->from; + if(strcmp(msg->body, "player left") == 0){ + if(p->state == Watching){ + leaveseat(&stands, p); + freeplayer(p); + }else{ + op = p == m->pl[0]? m->pl[1]: m->pl[0]; + chanprint(op->io.out, "win\n"); + op->state = Waiting0; + op->battle = nil; + freeplayer(p); + freemsg(msg); + goto Finish; } - if(debug) - fprint(2, "%d plays, %d waits\n", i^1, i); + }else if(strcmp(msg->body, "take seat") == 0){ + takeseat(&stands, p); + p->battle = m; + p->state = Watching; + }else if(strcmp(msg->body, "leave seat") == 0){ + leaveseat(&stands, p); + p->battle = nil; + p->state = Waiting0; } + + freemsg(msg); break; } - free(s); } Finish: if(debug) fprint(2, "[%d] battleproc ending\n", getpid()); - free(m); - chanfree(cp[0].c); - chanfree(cp[1].c); - threadkillgrp(threadgetgrp()); + free(stands.seats); + rmmatch(m); + freematch(m); threadexits(nil); } void matchmaker(void *) { + Msg *msg; Match *m; Player *pl[2]; + int i; threadsetname("matchmaker"); - for(;;){ - pl[0] = recvp(playerq); - pl[1] = recvp(playerq); - - pl[0]->state = Waiting0; - pl[1]->state = Waiting0; - memset(pl[0]->map, Twater, MAPW*MAPH); - memset(pl[1]->map, Twater, MAPW*MAPH); - m = emalloc(sizeof *m); - m->pl[0] = pl[0]; - m->pl[1] = pl[1]; - - proccreate(battleproc, m, mainstacksize); + i = 0; + + enum { QUE, CTL, NONE }; + Alt a[] = { + [QUE] {playerq, &pl[0], CHANRCV}, + [CTL] {mmctl, &msg, CHANRCV}, + [NONE] {nil, nil, CHANEND} + }; + for(;;) + switch(alt(a)){ + case QUE: + if(debug) fprint(2, "matchmaker got %d%s player fd %d\n", i+1, i == 0? "st": "nd", pl[i]->io.fd); + + pl[i]->state = Ready; + memset(pl[i]->map, Twater, MAPW*MAPH); + + if(++i > 1){ + m = newmatch(pl[0], pl[1]); + addmatch(m); + pl[0]->battle = m; + pl[1]->battle = m; + i = 0; + + proccreate(battleproc, m, mainstacksize); + } + a[QUE].v = &pl[i]; + break; + case CTL: + if(debug) fprint(2, "matchmaker rcvd '%s' from p(fd=%d)\n", msg->body, msg->from->io.fd); + + if(strcmp(msg->body, "player left") == 0){ + if(i == 1 && pl[0] == msg->from){ + i = 0; + a[QUE].v = &pl[0]; + freeplayer(pl[0]); + freemsg(msg); + }else + sendp(msg->from->battle->ctl, msg); + } + break; + } +} + +int +fprintmatches(int fd) +{ + Match *m; + int n; + + n = 0; + if(theater.next == &theater) + n += fprint(fd, "let there be peace\n"); + else for(n = 0, m = theater.next; m != &theater; m = m->next) + n += fprint(fd, "%d\t%s vs. %s\n", m->id, m->pl[0]->name, m->pl[1]->name); + return n; +} + +int +fprintmatch(int fd, Match *m) +{ + int n, i; + + n = 0; + if(m == nil) + n += fprint(fd, "no such match\n"); + else{ + n += fprint(fd, "id %d\n", m->id); + n += fprint(fd, "players\n"); + for(i = 0; i < nelem(m->pl); i++) + n += fprint(fd, "\t%d\n" + "\t\tname %s\n" + "\t\tstate %s\n" + "\t\taddr %s\n" + "\t\tfd %d\n", + i, m->pl[i]->name, statename(m->pl[i]->state), m->pl[i]->nci->raddr, m->pl[i]->io.fd); } + return n; +} + +/* + * Command & Control + * + * - show matches: prints ongoing matches + * - show match [mid]: prints info about a given match + * - debug [on|off]: toggles debug mode + */ +void +c2proc(void *) +{ + char buf[256], *user, *cmd[3]; + int fd, pfd[2], n, nc, mid; + + threadsetname("c2proc"); + + if(pipe(pfd) < 0) + sysfatal("pipe: %r"); + + user = getenv("user"); + snprint(buf, sizeof buf, "/srv/btsctl.%s.%d", user, getppid()); + free(user); + + fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600); + if(fd < 0) + sysfatal("open: %r"); + fprint(fd, "%d", pfd[0]); + close(pfd[0]); + + while((n = read(pfd[1], buf, sizeof(buf)-1)) > 0){ + buf[n] = 0; + + nc = tokenize(buf, cmd, nelem(cmd)); + if((nc == 2 || nc == 3) && strcmp(cmd[0], "show") == 0){ + if(nc == 2 && strcmp(cmd[1], "matches") == 0) + fprintmatches(pfd[1]); + else if(nc == 3 && strcmp(cmd[1], "match") == 0){ + mid = strtoul(cmd[2], nil, 10); + fprintmatch(pfd[1], getmatch(mid)); + } + }else if(nc == 2 && strcmp(cmd[0], "debug") == 0){ + if(strcmp(cmd[1], "on") == 0) + debug = 1; + else if(strcmp(cmd[1], "off") == 0) + debug = 0; + } + } + + threadexitsall("fleet admiral drowned"); } void dolisten(char *addr) { - char adir[40], ldir[40], aux[128], *s; - int acfd, lcfd, dfd, sfd; + char adir[40], ldir[40]; + int acfd, lcfd, dfd; Player *p; acfd = announce(addr, adir); @@ -253,19 +601,8 @@ dolisten(char *addr) while((lcfd = listen(adir, ldir)) >= 0){ if((dfd = accept(lcfd, ldir)) >= 0){ - fd2path(dfd, aux, sizeof aux); - s = strrchr(aux, '/'); - *s = 0; - snprint(aux, sizeof aux, "%s/status", aux); - sfd = open(aux, OREAD); - if(sfd < 0) - sysfatal("open: %r"); - - p = emalloc(sizeof *p); - p->fd = dfd; - p->sfd = sfd; - p->state = Waiting0; - sendp(playerq, p); + p = newplayer(dfd); + proccreate(playerproc, p, mainstacksize); } close(lcfd); } @@ -300,6 +637,11 @@ threadmain(int argc, char *argv[]) usage(); playerq = chancreate(sizeof(Player*), 8); + msgq = chancreate(sizeof(Msg*), 8); + mmctl = chancreate(sizeof(Msg*), 8); + theater.next = theater.prev = &theater; + proccreate(c2proc, nil, mainstacksize); proccreate(matchmaker, nil, mainstacksize); + proccreate(operator, nil, mainstacksize); dolisten(addr); } -- cgit v1.2.3