uni

University stuff
git clone git://git.margiolis.net/uni.git
Log | Files | Refs | README | LICENSE

rps_server.c (9165B)


      1 #include <sys/mman.h>
      2 #include <sys/socket.h>
      3 #include <sys/types.h>
      4 #include <sys/wait.h>
      5 
      6 #include <arpa/inet.h>
      7 #include <netdb.h>
      8 #include <netinet/in.h>
      9 
     10 #include <err.h>
     11 #include <signal.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <unistd.h>
     16 
     17 #include "extern.h"
     18 
     19 struct client {
     20 	int fd;
     21 	int id;
     22 	char name[NAME_LEN];
     23 	int move;
     24 	int nplays;
     25 	int nwins;
     26 	struct client *opponent;
     27 };
     28 
     29 static void remove_client(struct client *);
     30 static int srv(int);
     31 static void sighandler(int);
     32 static void usage(void);
     33 
     34 static int *ids;
     35 static int *nclients;
     36 static struct client *clients;
     37 static volatile sig_atomic_t f_quit = 0;
     38 
     39 static void
     40 remove_client(struct client *c)
     41 {
     42 	struct client *cp;
     43 	int i;
     44 
     45 	if (c->id < 1)
     46 		return;
     47 	for (i = 0; i < CLIENTS_MAX; i++) {
     48 		cp = &clients[i];
     49 		if (cp == c) {
     50 			printf("%s: client disconnected: %s#%d (fd=%d)\n",
     51 			    getprogname(), cp->name, cp->id, cp->fd);
     52 			close(cp->fd);
     53 			memset(cp, 0, sizeof(*cp));
     54 			(*nclients)--;
     55 			break;
     56 		}
     57 	}
     58 }
     59 
     60 static int
     61 srv(int fd)
     62 {
     63 	struct client *c, *cp;
     64 	char cmd[CMD_LEN], buf[BUF_LEN];
     65 	size_t len;
     66 	int rc = 0, i, tmp, quit, id;
     67 
     68 	c = &clients[*nclients];
     69 	memset(c, 0, sizeof(*c));
     70 	c->fd = fd;
     71 	c->id = ++(*ids);
     72 	c->move = -1;
     73 
     74 	(*nclients)++;
     75 
     76 	if (recv(c->fd, c->name, sizeof(c->name), 0) < 0) {
     77 		warn("recv(%d, nick)", c->fd);
     78 		remove_client(c);
     79 		return (-1);
     80 	}
     81 
     82 	printf("%s: active clients=%d\n", getprogname(), *nclients);
     83 	printf("%s: client connected: %s#%d (fd=%d)\n",
     84 	    getprogname(), c->name, c->id, c->fd);
     85 
     86 	for (;;) {
     87 		memset(buf, 0, sizeof(buf));
     88 		tmp = recv(c->fd, cmd, sizeof(cmd), 0);
     89 		if (tmp < 0) {
     90 			warn("recv(%d, cmd)", c->fd);
     91 			rc = -1;
     92 			break;
     93 		} else if (tmp == 0)
     94 			break;
     95 
     96 		if (strcmp(cmd, "challenge") == 0) {
     97 			if (recv(c->fd, &id, sizeof(id), 0) < 0) {
     98 				warn("recv(%d, challenge, id)", c->fd);
     99 				rc = -1;
    100 				break;
    101 			}
    102 			if (recv(c->fd, &c->move, sizeof(c->move), 0) < 0) {
    103 				warn("recv(%d, challenge, move)", c->fd);
    104 				rc = -1;
    105 				break;
    106 			}
    107 			if (c->id == id) {
    108 				warnx("%s tried to challenge themselves",
    109 				    c->name);
    110 				continue;
    111 			}
    112 			cp = NULL;
    113 			for (i = 0; i < CLIENTS_MAX; i++) {
    114 				cp = &clients[i];
    115 				if (cp->id > 0 && cp->id == id)
    116 					break;
    117 				cp = NULL;
    118 			}
    119 			/* TODO add lock */
    120 			if (cp == NULL) {
    121 				warnx("id=%d not found", id);
    122 				continue;
    123 			}
    124 			if (cp->opponent != NULL) {
    125 				warnx("%s is already challenged", cp->name);
    126 				continue;
    127 			}
    128 			c->opponent = cp;
    129 			cp->opponent = c;
    130 
    131 			printf("%s is challenging %s with %s\n",
    132 			    c->name, cp->name, move2str(c->move));
    133 			for (i = 0; i < MOVE_TIMEOUT; i++) {
    134 				if (c->opponent != NULL)
    135 					sleep(1);
    136 				else
    137 					break;
    138 			}
    139 			printf("%s woke up\n", c->name);
    140 			if (c->opponent != NULL) {
    141 				printf("%s didn't make a move\n", c->opponent->name);
    142 				c->nwins++;
    143 				c->move = -1;
    144 				c->nplays++;
    145 				c->opponent->move = -1;
    146 				c->opponent->nplays++;
    147 				c->opponent->opponent = NULL;
    148 				c->opponent = NULL;
    149 			}
    150 		} else if (strcmp(cmd, "accept") == 0) {
    151 			if (recv(c->fd, &c->move, sizeof(c->move), 0) < 0) {
    152 				warn("recv(%d, accept)", c->fd);
    153 				rc = -1;
    154 				break;
    155 			}
    156 
    157 			if (c->opponent == NULL) {
    158 				warnx("%s: cannot play when unchallenged",
    159 				    c->name);
    160 				continue;
    161 			}
    162 
    163 			switch (c->move) {
    164 			case MOVE_ROCK:
    165 				if (c->opponent->move == MOVE_SCISSOR)
    166 					c->nwins++;
    167 				else if (c->opponent->move == MOVE_PAPER)
    168 					c->opponent->nwins++;
    169 				break;
    170 			case MOVE_PAPER:
    171 				if (c->opponent->move == MOVE_ROCK)
    172 					c->nwins++;
    173 				else if (c->opponent->move == MOVE_SCISSOR)
    174 					c->opponent->nwins++;
    175 				break;
    176 			case MOVE_SCISSOR:
    177 				if (c->opponent->move == MOVE_PAPER)
    178 					c->nwins++;
    179 				else if (c->opponent->move == MOVE_ROCK)
    180 					c->opponent->nwins++;
    181 				break;
    182 			default:
    183 				warnx("invalid move: %s", move2str(c->move));
    184 				continue;
    185 			}
    186 			printf("%s (%d) - %s (%d)\n",
    187 			    c->name, c->nwins,
    188 			    c->opponent->name, c->opponent->nwins);
    189 
    190 			c->move = -1;
    191 			c->nplays++;
    192 			c->opponent->move = -1;
    193 			c->opponent->nplays++;
    194 			c->opponent->opponent = NULL;
    195 			c->opponent = NULL;
    196 		} else if (strcmp(cmd, "msg") == 0) {
    197 			if (recv(c->fd, &len, sizeof(len), 0) < 0) {
    198 				warn("recv(%d, msg, len)", c->fd);
    199 				rc = -1;
    200 				break;
    201 			}
    202 			if (len < 0 || len > BUF_LEN) {
    203 				warnx("invalid length: %ld", len);
    204 				continue;
    205 			}
    206 			if (recv(c->fd, buf, len, 0) < 0) {
    207 				warn("recv(%d, msg, msg)", c->fd);
    208 				rc = -1;
    209 				break;
    210 			}
    211 			buf[len] = '\0';
    212 			printf("<%s> %s\n", c->name, buf);
    213 			/* TODO send message to client */
    214 		} else if (strcmp(cmd, "list") == 0) {
    215 			snprintf(buf, sizeof(buf),
    216 			    "ID\tNAME\tPLAYED\tWON\n");
    217 			for (i = 0; i < CLIENTS_MAX; i++) {
    218 				cp = &clients[i];
    219 				if (cp->id == 0)
    220 					continue;
    221 				snprintf(buf + strlen(buf), sizeof(buf),
    222 				    "%d\t%s\t%d\t%d\n",
    223 				    cp->id, cp->name, cp->nplays, cp->nwins);
    224 			}
    225 			if (send(c->fd, buf, sizeof(buf), 0) < 0) {
    226 				warn("send(%d, list)", c->fd);
    227 				rc = -1;
    228 				break;
    229 			}
    230 		} else if (strcmp(cmd, "quit") == 0) {
    231 			break;
    232 		} else {
    233 			warnx("received unknown command: %s", cmd);
    234 		}
    235 	}
    236 
    237 	remove_client(c);
    238 
    239 	return (rc);
    240 }
    241 
    242 static void
    243 sighandler(int sig)
    244 {
    245 	f_quit = 1;
    246 }
    247 
    248 static void
    249 usage(void)
    250 {
    251 	fprintf(stderr, "usage: %1$s [-b backlog] [-p port] <hostname|ipv4_addr>\n",
    252 	    getprogname());
    253 	exit(1);
    254 }
    255 
    256 int
    257 main(int argc, char *argv[])
    258 {
    259 	struct sockaddr_in sin;
    260 	struct hostent *hp;
    261 	struct sigaction sa;
    262 	int backlog = 10;
    263 	int port = 9999;
    264 	int sfd, cfd;
    265 	int ch, i;
    266 
    267 	while ((ch = getopt(argc, argv, "b:p:")) != -1) {
    268 		switch (ch) {
    269 		case 'b':
    270 			/* 
    271 			 * Negative `backlog` value normally requests the
    272 			 * maximum allowable value (HISTORY section of
    273 			 * listen(2)'s FreeBSD man page), but it's better to
    274 			 * not allow it in case the user passes a negative
    275 			 * value accidentally. Also a value of 0 doesn't make
    276 			 * sense, so we don't allow it either.
    277 			 */
    278 			if ((backlog = atoi(optarg)) < 1)
    279 				errx(1, "backlog value must be greater than 1");
    280 			break;
    281 		case 'p':
    282 			/* Choose custom port but don't use a well-known port. */
    283 			if ((port = atoi(optarg)) < 1024)
    284 				errx(1, "can't use port number < 1024");
    285 			break;
    286 		case '?':
    287 		default:
    288 			usage();
    289 		}
    290 	}
    291 	argc -= optind;
    292 	argv += optind;
    293 
    294 	/* Hostname/IP missing. */
    295 	if (argc < 1)
    296 		usage();
    297 
    298 	/*
    299 	 * Handle termination signals so we don't exit abnormally (i.e without
    300 	 * cleaning up resources).
    301 	 */
    302 	memset(&sa, 0, sizeof(sa));
    303 	sigfillset(&sa.sa_mask);
    304 	sa.sa_handler = sighandler;
    305 	if (sigaction(SIGINT, &sa, NULL) < 0)
    306 		err(1, "sigaction(SIGINT)");
    307 	if (sigaction(SIGTERM, &sa, NULL) < 0)
    308 		err(1, "sigaction(SIGTERM)");
    309 
    310 	if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    311 		err(1, "socket(AF_INET)");
    312 	memset(&sin, 0, sizeof(sin));
    313 	sin.sin_family = AF_INET;
    314 	/* Convert the port number to network bytes. */
    315 	sin.sin_port = htons(port);
    316 
    317 	/* 
    318 	 * We'll try and see if the input is an IPv4 address. If inet_aton(3)
    319 	 * does not fail, the user did pass an IPv4 address. However if
    320 	 * inet_addr(3) fails, we'll assume the user passed a hostname. If the
    321 	 * hostname was wrong, gethostbyname(3) will fail. That lets us use
    322 	 * both hostnames and IPv4 addresses as arguments.
    323 	 */
    324 	if (!inet_aton(*argv, &sin.sin_addr)) {
    325 		/* 
    326 		 * Get host info by hostname. The host's IPv4 address will be
    327 		 * written to `hp->h_addr`.
    328 		 */
    329 		if ((hp = gethostbyname(*argv)) == NULL)
    330 			errx(1, "gethostbyname(%s) failed", *argv);
    331 		memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    332 	}
    333 	if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    334 		err(1, "bind");
    335 
    336 	printf("Domain: AF_INET\nIPv4: %s\nPort: %d\nBacklog: %d\n",
    337 	    inet_ntoa(sin.sin_addr), port, backlog);
    338 
    339 	if (listen(sfd, backlog) < 0)
    340 		err(1, "listen");
    341 
    342 	/* Use mmap(2) that we can modify them from the child. */
    343 	nclients = mmap(NULL, sizeof(*nclients), PROT_READ | PROT_WRITE,
    344 	    MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    345 	if (nclients == MAP_FAILED)
    346 		err(1, "mmap");
    347 	*nclients = 0;
    348 
    349 	clients = mmap(NULL, sizeof(struct client) * CLIENTS_MAX,
    350 	    PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    351 	if (clients == MAP_FAILED)
    352 		err(1, "mmap");
    353 	memset(clients, 0, sizeof(struct client) * CLIENTS_MAX);
    354 
    355 	ids = mmap(NULL, sizeof(*ids), PROT_READ | PROT_WRITE,
    356 	    MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    357 	if (ids == MAP_FAILED)
    358 		err(1, "mmap");
    359 	*ids = 0;
    360 
    361 	for (;;) {
    362 		/* We caught a termination signal. */
    363 		if (f_quit)
    364 			break;
    365 		/* TODO */
    366 		/*if ((*nclients + 1) >= CLIENTS_MAX)*/
    367 			/*continue;*/
    368 		if ((cfd = accept(sfd, NULL, NULL)) < 0) {
    369 			warn("accept(%d)", cfd);
    370 			continue;
    371 		}
    372 		/* 
    373 		 * Create a child process to serve the client so the parent can
    374 		 * continue waiting for another client to serve.
    375 		 */
    376 		switch (fork()) {
    377 		case -1:
    378 			err(1, "fork");
    379 		case 0:
    380 			if (srv(cfd) < 0)
    381 				warnx("srv(%d) failed", cfd);
    382 			_exit(0);
    383 		default:
    384 			close(cfd);
    385 		}
    386 	}
    387 
    388 	/* Will get here only if a termination signal is caught. */
    389 	close(sfd);
    390 	for (i = 0; i < CLIENTS_MAX; i++)
    391 		remove_client(&clients[i]);
    392 	munmap(ids, sizeof(*ids));
    393 	munmap(nclients, sizeof(*nclients));
    394 	munmap(clients, sizeof(struct client) * CLIENTS_MAX);
    395 
    396 	return (0);
    397 }