commit 7e43536e1f58aff2a918d9b8c435dd1ae4995327
parent a93b9c0ff9b184453a95cdde7edbe3700c5a4613
Author: Christos Margiolis <christos@FreeBSD.org>
Date: Tue, 11 Jun 2024 01:33:05 +0200
bar
Diffstat:
4 files changed, 290 insertions(+), 161 deletions(-)
diff --git a/concurrent_programming/Makefile b/concurrent_programming/Makefile
@@ -1,6 +1,6 @@
all:
cc rps_server.c -o rps_server
- cc rps_client.c -lncurses -o rps_client
+ cc rps_client.c -o rps_client
clean:
rm -f rps_server rps_client *.core *.o
diff --git a/concurrent_programming/extern.h b/concurrent_programming/extern.h
@@ -3,7 +3,9 @@
#define CMD_LEN 32
#define NAME_LEN 32
-#define MOVE_TIMEOUT 3
+#define CLIENTS_MAX 64
+#define MOVE_TIMEOUT 10
+#define BUF_LEN (BUFSIZ << 2)
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
enum {
@@ -12,4 +14,39 @@ enum {
MOVE_SCISSOR,
};
+static inline int str2move(const char *);
+static inline const char *move2str(int);
+
+static inline int
+str2move(const char *str)
+{
+ int move;
+
+ if (strcmp(str, "rock") == 0)
+ move = MOVE_ROCK;
+ else if (strcmp(str, "paper") == 0)
+ move = MOVE_PAPER;
+ else if (strcmp(str, "scissor") == 0)
+ move = MOVE_SCISSOR;
+ else
+ move = -1;
+
+ return (move);
+}
+
+static inline const char *
+move2str(int move)
+{
+ switch (move) {
+ case MOVE_ROCK:
+ return ("rock");
+ case MOVE_PAPER:
+ return ("paper");
+ case MOVE_SCISSOR:
+ return ("scissor");
+ default:
+ return ("unknown");
+ }
+}
+
#endif /* _EXTERN_H_ */
diff --git a/concurrent_programming/rps_client.c b/concurrent_programming/rps_client.c
@@ -16,13 +16,15 @@
#include "extern.h"
+#define NTDS 2
+
struct command {
char name[CMD_LEN];
int (*func)(char *);
};
static int cmd_challenge(char *);
-static int cmd_play(char *);
+static int cmd_accept(char *);
static int cmd_msg(char *);
static int cmd_list(char *);
static int cmd_quit(char *);
@@ -32,27 +34,42 @@ static void sighandler(int);
static void usage(void);
static struct command commands[] = {
- { "challenge", cmd_challenge }, /* /challenge <id> */
- { "play", cmd_play }, /* /play <rock|paper|scissor> */
- { "msg", cmd_msg }, /* /msg <message> */
- { "list", cmd_list }, /* /list */
- { "quit", cmd_quit }, /* /quit */
- { "help", cmd_help }, /* /help */
+ { "challenge", cmd_challenge },/* /challenge <id> <rock|paper|scissor> */
+ { "accept", cmd_accept }, /* /accept <rock|paper|scissor> */
+ { "msg", cmd_msg }, /* /msg <message> */
+ { "list", cmd_list }, /* /list */
+ { "quit", cmd_quit }, /* /quit */
+ { "help", cmd_help }, /* /help */
};
static int ymax;
static int xmax;
-static int f_quit = 0;
static int fd;
+static volatile sig_atomic_t f_quit = 0;
static int
cmd_challenge(char *args)
{
- char cmd[CMD_LEN];
- int id;
+ char cmd[CMD_LEN], *s;
+ int id, move;
if (args == NULL) {
- warnx("usage: /challenge <id>");
+ warnx("usage: /challenge <id> <rock|paper|scissor>");
+ return (-1);
+ }
+ s = strchr(args, ' ');
+ if (s == NULL || *s == '\0') {
+ warnx("usage: /challenge <id> <rock|paper|scissor>");
+ return (-1);
+ }
+ while (*s == ' ')
+ s++;
+ if (*s == '\0') {
+ warnx("usage: /challenge <id> <rock|paper|scissor>");
+ return (-1);
+ }
+ if ((move = str2move(s)) < 0) {
+ warnx("invalid move: %s", s);
return (-1);
}
@@ -72,11 +89,16 @@ cmd_challenge(char *args)
return (-1);
}
+ if (send(fd, &move, sizeof(move), 0) < 0) {
+ warn("send(challenge, move)");
+ return (-1);
+ }
+
return (0);
}
static int
-cmd_play(char *args)
+cmd_accept(char *args)
{
char cmd[CMD_LEN];
int move;
@@ -86,23 +108,19 @@ cmd_play(char *args)
return (-1);
}
- strlcpy(cmd, "play", sizeof(cmd));
+ strlcpy(cmd, "accept", sizeof(cmd));
if (send(fd, cmd, sizeof(cmd), 0) < 0) {
- warn("send(play, cmd)");
+ warn("send(accept, cmd)");
return (-1);
}
- if (strcmp(args, "rock") == 0)
- move = MOVE_ROCK;
- else if (strcmp(args, "paper") == 0)
- move = MOVE_PAPER;
- else if (strcmp(args, "scissor") == 0)
- move = MOVE_SCISSOR;
- else
- warnx("invalid move: %s\n", args);
+ if ((move = str2move(args)) < 0) {
+ warnx("invalid move: %s", args);
+ return (-1);
+ }
if (send(fd, &move, sizeof(move), 0) < 0) {
- warn("send(play, move)");
+ warn("send(accept, move)");
return (-1);
}
@@ -113,6 +131,7 @@ static int
cmd_msg(char *args)
{
char cmd[CMD_LEN];
+ size_t len;
if (args == NULL) {
warnx("usage: /msg <message>");
@@ -125,13 +144,24 @@ cmd_msg(char *args)
return (-1);
}
+ len = strlen(args) + 1;
+ if (send(fd, &len, sizeof(len), 0) < 0) {
+ warn("send(msg, len)");
+ return (-1);
+ }
+
+ if (send(fd, args, len, 0) < 0) {
+ warn("send(msg, msg)");
+ return (-1);
+ }
+
return (0);
}
static int
cmd_list(char *args)
{
- char cmd[CMD_LEN];
+ char cmd[CMD_LEN], buf[BUF_LEN];
strlcpy(cmd, "list", sizeof(cmd));
if (send(fd, cmd, sizeof(cmd), 0) < 0) {
@@ -139,6 +169,12 @@ cmd_list(char *args)
return (-1);
}
+ if (recv(fd, buf, sizeof(buf), 0) < 0) {
+ warn("recv(list)");
+ return (-1);
+ }
+ puts(buf);
+
return (0);
}
@@ -162,8 +198,9 @@ static int
cmd_help(char *args)
{
printf("Available commands:\n");
- printf("/challenge <id|name>\t\tChallenge a player\n");
- printf("/play <rock|paper|scisssor>\tMake a move\n");
+ printf("/challenge <id|name> <rock|paper|scissor>\t\t"
+ "Challenge a player\n");
+ printf("/accept <rock|paper|scisssor>\tAccept challenge\n");
printf("/msg <message>\t\t\tSend a message to the global chat\n");
printf("/quit\t\t\t\tQuit the game\n");
printf("/help\t\t\t\tShow this help message\n");
@@ -174,7 +211,7 @@ cmd_help(char *args)
static struct command *
parse_command(char **args)
{
- char buf[BUFSIZ], *line, *cmd, *s;
+ char buf[BUF_LEN], *line, *cmd, *s;
int i;
printf("\r> ");
@@ -255,7 +292,8 @@ main(int argc, char *argv[])
struct hostent *hp;
struct sigaction sa;
struct command *cmd;
- char *args, *s;
+ char *args;
+ char *s;
char nick[NAME_LEN];
int port = 9999;
int ch;
@@ -311,6 +349,7 @@ main(int argc, char *argv[])
break;
}
+ /* Set up connection. */
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err(1, "socket(AF_INET)");
memset(&sin, 0, sizeof(sin));
@@ -328,12 +367,13 @@ main(int argc, char *argv[])
if (send(fd, nick, sizeof(nick), 0) < 0)
err(1, "send(nick)");
+ /* Print list of available players. */
cmd_list(NULL);
+ /* Main game loop. */
for (;;) {
if (f_quit)
break;
- /* TODO redraw */
if ((cmd = parse_command(&args)) == NULL)
continue;
if (cmd->func(args) < 0)
@@ -341,6 +381,7 @@ main(int argc, char *argv[])
if (args != NULL)
free(args);
}
+
close(fd);
return (0);
diff --git a/concurrent_programming/rps_server.c b/concurrent_programming/rps_server.c
@@ -1,5 +1,4 @@
#include <sys/mman.h>
-#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -17,147 +16,216 @@
#include "extern.h"
-struct user {
+struct client {
int fd;
int id;
char name[NAME_LEN];
int move;
int nplays;
int nwins;
- struct user *opponent;
- TAILQ_ENTRY(user) next;
+ struct client *opponent;
};
-static int srv(struct user *);
+static void remove_client(struct client *);
+static int srv(int);
static void sighandler(int);
static void usage(void);
+static int *ids;
+static int *nclients;
+static struct client *clients;
static volatile sig_atomic_t f_quit = 0;
-static int ids = 0;
-static int *nusers;
-static TAILQ_HEAD(, user) users = TAILQ_HEAD_INITIALIZER(users);
static void
-remove_user(struct user *user)
+remove_client(struct client *c)
{
- struct user *up;
-
- while (!TAILQ_EMPTY(&users)) {
- up = TAILQ_FIRST(&users);
- TAILQ_REMOVE(&users, up, next);
- close(up->fd);
- munmap(up, sizeof(struct user));
- break;
+ struct client *cp;
+ int i;
+
+ if (c->id < 1)
+ return;
+ for (i = 0; i < CLIENTS_MAX; i++) {
+ cp = &clients[i];
+ if (cp == c) {
+ printf("%s: client disconnected: %s#%d (fd=%d)\n",
+ getprogname(), cp->name, cp->id, cp->fd);
+ close(cp->fd);
+ memset(cp, 0, sizeof(*cp));
+ (*nclients)--;
+ break;
+ }
}
}
static int
-srv(struct user *user)
+srv(int fd)
{
- struct user *up;
- char cmd[CMD_LEN];
- int rc = 0, tmp, quit, id;
+ struct client *c, *cp;
+ char cmd[CMD_LEN], buf[BUF_LEN];
+ size_t len;
+ int rc = 0, i, tmp, quit, id;
+
+ c = &clients[*nclients];
+ memset(c, 0, sizeof(*c));
+ c->fd = fd;
+ c->id = ++(*ids);
+ c->move = -1;
+
+ (*nclients)++;
+
+ if (recv(c->fd, c->name, sizeof(c->name), 0) < 0) {
+ warn("recv(%d, nick)", c->fd);
+ remove_client(c);
+ return (-1);
+ }
+
+ printf("%s: active clients=%d\n", getprogname(), *nclients);
+ printf("%s: client connected: %s#%d (fd=%d)\n",
+ getprogname(), c->name, c->id, c->fd);
for (;;) {
- tmp = recv(user->fd, cmd, sizeof(cmd), 0);
+ memset(buf, 0, sizeof(buf));
+ tmp = recv(c->fd, cmd, sizeof(cmd), 0);
if (tmp < 0) {
- warn("recv(%d, cmd)", user->fd);
+ warn("recv(%d, cmd)", c->fd);
rc = -1;
break;
} else if (tmp == 0)
break;
if (strcmp(cmd, "challenge") == 0) {
- if (recv(user->fd, &id, sizeof(id), 0) < 0) {
- warn("recv(%d, id)", user->fd);
+ if (recv(c->fd, &id, sizeof(id), 0) < 0) {
+ warn("recv(%d, challenge, id)", c->fd);
+ rc = -1;
+ break;
+ }
+ if (recv(c->fd, &c->move, sizeof(c->move), 0) < 0) {
+ warn("recv(%d, challenge, move)", c->fd);
rc = -1;
break;
}
- up = NULL;
- TAILQ_FOREACH(up, &users, next) {
- if (up->id == id)
+ if (c->id == id) {
+ warnx("%s tried to challenge themselves",
+ c->name);
+ continue;
+ }
+ cp = NULL;
+ for (i = 0; i < CLIENTS_MAX; i++) {
+ cp = &clients[i];
+ if (cp->id > 0 && cp->id == id)
break;
+ cp = NULL;
}
- if (up == NULL) {
+ if (cp == NULL) {
warnx("id=%d not found", id);
continue;
}
- if (up->opponent != NULL) {
- warnx("%s is already challenged", up->name);
+ if (cp->opponent != NULL) {
+ warnx("%s is already challenged", cp->name);
continue;
}
- user->opponent = up;
- up->opponent = user;
- printf("%s is challenging %d\n", user->name, id);
- /* TODO challenge available user */
- } else if (strcmp(cmd, "play") == 0) {
- if (recv(user->fd, &user->move,
- sizeof(user->move), 0) < 0) {
- warn("recv(%d, play)", user->fd);
+ c->opponent = cp;
+ cp->opponent = c;
+
+ printf("%s is challenging %s with %s\n",
+ c->name, cp->name, move2str(c->move));
+ for (i = 0; i < MOVE_TIMEOUT; i++) {
+ if (c->opponent != NULL)
+ sleep(1);
+ else
+ break;
+ }
+ printf("%s woke up\n", c->name);
+ if (c->opponent != NULL) {
+ printf("%s didn't make a move\n", c->opponent->name);
+ c->nwins++;
+ c->move = -1;
+ c->nplays++;
+ c->opponent->move = -1;
+ c->opponent->nplays++;
+ c->opponent->opponent = NULL;
+ c->opponent = NULL;
+ }
+ } else if (strcmp(cmd, "accept") == 0) {
+ if (recv(c->fd, &c->move, sizeof(c->move), 0) < 0) {
+ warn("recv(%d, accept)", c->fd);
rc = -1;
break;
}
- /* Cannot play when unchallenged. */
- if (user->opponent == NULL)
+ if (c->opponent == NULL) {
+ warnx("%s: cannot play when unchallenged",
+ c->name);
continue;
-
- /* Give the opponent 3 seconds to make a move. */
- for (int i = 0; user->opponent->move < 0 &&
- i < MOVE_TIMEOUT; i++) {
- printf("[%s - %s] %s sleeping\n",
- user->name, user->opponent->name, user->name);
- sleep(1);
- }
-
- /* Didn't respond, he lost. */
- if (user->opponent->move < 0) {
- printf("[%s - %s] opponent didn't respond\n",
- user->name, user->opponent->name);
- user->move = -1;
- user->opponent = NULL;
- /*user->opponent->opponent = NULL;*/
- user->nplays++;
- user->nwins++;
}
- printf("%s=%d, %s=%d\n", user->name, user->move,
- user->opponent->name, user->opponent->move);
-
- switch (user->move) {
+ switch (c->move) {
case MOVE_ROCK:
- if (user->opponent->move == MOVE_SCISSOR)
- user->nwins++;
+ if (c->opponent->move == MOVE_SCISSOR)
+ c->nwins++;
+ else if (c->opponent->move == MOVE_PAPER)
+ c->opponent->nwins++;
break;
case MOVE_PAPER:
- if (user->opponent->move == MOVE_ROCK)
- user->nwins++;
+ if (c->opponent->move == MOVE_ROCK)
+ c->nwins++;
+ else if (c->opponent->move == MOVE_SCISSOR)
+ c->opponent->nwins++;
break;
case MOVE_SCISSOR:
- if (user->opponent->move == MOVE_PAPER)
- user->nwins++;
+ if (c->opponent->move == MOVE_PAPER)
+ c->nwins++;
+ else if (c->opponent->move == MOVE_ROCK)
+ c->opponent->nwins++;
break;
default:
- warnx("invalid move: %d", user->move);
+ warnx("invalid move: %s", move2str(c->move));
continue;
}
- printf("[%s - %s] = [%d - %d]\n",
- user->name, user->opponent->name,
- user->nwins, user->opponent->nwins);
-
- user->move = -1;
- user->opponent = NULL;
- user->opponent->opponent = NULL;
- user->nplays++;
+ printf("%s (%d) - %s (%d)\n",
+ c->name, c->nwins,
+ c->opponent->name, c->opponent->nwins);
+
+ c->move = -1;
+ c->nplays++;
+ c->opponent->move = -1;
+ c->opponent->nplays++;
+ c->opponent->opponent = NULL;
+ c->opponent = NULL;
} else if (strcmp(cmd, "msg") == 0) {
- /* TODO post message to global chat (list) */
+ if (recv(c->fd, &len, sizeof(len), 0) < 0) {
+ warn("recv(%d, msg, len)", c->fd);
+ rc = -1;
+ break;
+ }
+ if (len < 0 || len > BUF_LEN) {
+ warnx("invalid length: %ld", len);
+ continue;
+ }
+ if (recv(c->fd, buf, len, 0) < 0) {
+ warn("recv(%d, msg, msg)", c->fd);
+ rc = -1;
+ break;
+ }
+ buf[len] = '\0';
+ printf("<%s> %s\n", c->name, buf);
+ /* TODO send message to client */
} else if (strcmp(cmd, "list") == 0) {
- printf("ID\tNAME\tPLAYED\tWON\n");
- TAILQ_FOREACH(up, &users, next) {
- printf("%d\t%s\t%d\t%d\n",
- up->id, up->name, up->nplays, up->nwins);
+ snprintf(buf, sizeof(buf),
+ "ID\tNAME\tPLAYED\tWON\n");
+ for (i = 0; i < CLIENTS_MAX; i++) {
+ cp = &clients[i];
+ if (cp->id == 0)
+ continue;
+ snprintf(buf + strlen(buf), sizeof(buf),
+ "%d\t%s\t%d\t%d\n",
+ cp->id, cp->name, cp->nplays, cp->nwins);
+ }
+ if (send(c->fd, buf, sizeof(buf), 0) < 0) {
+ warn("send(%d, list)", c->fd);
+ rc = -1;
+ break;
}
- /* TODO send as nvlist */
} else if (strcmp(cmd, "quit") == 0) {
break;
} else {
@@ -165,9 +233,7 @@ srv(struct user *user)
}
}
- printf("%s: client disconnected: %s#%d (fd=%d)\n",
- getprogname(), user->name, user->id, user->fd);
- remove_user(user);
+ remove_client(c);
return (rc);
}
@@ -192,11 +258,10 @@ main(int argc, char *argv[])
struct sockaddr_in sin;
struct hostent *hp;
struct sigaction sa;
- struct user *user;
int backlog = 10;
int port = 9999;
- int sfd;
- int ch;
+ int sfd, cfd;
+ int ch, i;
while ((ch = getopt(argc, argv, "b:p:")) != -1) {
switch (ch) {
@@ -273,52 +338,35 @@ main(int argc, char *argv[])
if (listen(sfd, backlog) < 0)
err(1, "listen");
- /* mmap(2) this that we can modify decrement it from the child. */
- nusers = mmap(NULL, sizeof(*nusers), PROT_READ | PROT_WRITE,
+ /* Use mmap(2) that we can modify them from the child. */
+ nclients = mmap(NULL, sizeof(*nclients), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if (nusers == MAP_FAILED)
+ if (nclients == MAP_FAILED)
+ err(1, "mmap");
+ *nclients = 0;
+
+ clients = mmap(NULL, sizeof(struct client) * CLIENTS_MAX,
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (clients == MAP_FAILED)
err(1, "mmap");
- *nusers = 0;
+ memset(clients, 0, sizeof(struct client) * CLIENTS_MAX);
- TAILQ_INIT(&users);
+ ids = mmap(NULL, sizeof(*ids), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (ids == MAP_FAILED)
+ err(1, "mmap");
+ *ids = 0;
for (;;) {
/* We caught a termination signal. */
if (f_quit)
break;
-
- /*
- * Allocate user structure using mmap(2) so that we can modify
- * the userlist from the child process.
- */
- user = mmap(NULL, sizeof(struct user), PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if (user == MAP_FAILED)
- err(1, "mmap");
-
- if ((user->fd = accept(sfd, NULL, NULL)) < 0) {
- warn("accept(%d)", user->fd);
- remove_user(user);
+ if ((*nclients + 1) > CLIENTS_MAX)
continue;
- }
- /* Receive nickname and assign ID to user. */
- if (recv(user->fd, user->name, sizeof(user->name), 0) < 0) {
- warn("recv(%d, nick)", user->fd);
- remove_user(user);
+ if ((cfd = accept(sfd, NULL, NULL)) < 0) {
+ warn("accept(%d)", cfd);
continue;
}
-
- user->id = ++ids;
- user->nplays = 0;
- user->nwins = 0;
- user->opponent = NULL;
- TAILQ_INSERT_TAIL(&users, user, next);
-
- (*nusers)++;
-
- printf("%s: active users=%d\n", getprogname(), *nusers);
- printf("%s: client connected: %s#%d (fd=%d)\n",
- getprogname(), user->name, user->id, user->fd);
/*
* Create a child process to serve the client so the parent can
* continue waiting for another client to serve.
@@ -327,18 +375,21 @@ main(int argc, char *argv[])
case -1:
err(1, "fork");
case 0:
- if (srv(user) < 0)
- warnx("srv(%s#%d) failed", user->name, user->id);
- (*nusers)--;
+ if (srv(cfd) < 0)
+ warnx("srv(%d) failed", cfd);
_exit(0);
default:
- close(user->fd);
+ close(cfd);
}
}
/* Will get here only if a termination signal is caught. */
close(sfd);
- remove_user(user);
+ for (i = 0; i < CLIENTS_MAX; i++)
+ remove_client(&clients[i]);
+ munmap(ids, sizeof(*ids));
+ munmap(nclients, sizeof(*nclients));
+ munmap(clients, sizeof(struct client) * CLIENTS_MAX);
return (0);
}