uni

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

ex3_server.c (7207B)


      1 #include <sys/socket.h>
      2 #include <sys/types.h>
      3 #include <sys/un.h>
      4 
      5 #include <arpa/inet.h>
      6 #include <netdb.h>
      7 #include <netinet/in.h>
      8 
      9 #include <err.h>
     10 #include <signal.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <unistd.h>
     15 
     16 /*
     17  * Εργαστήριο ΛΣ2 (Δ6) / Εργασία 2: Άσκηση 3 (server) / 2020-2021
     18  * Ονοματεπώνυμο: Χρήστος Μαργιώλης
     19  * ΑΜ: 19390133
     20  * Τρόπος μεταγλώττισης: `cc ex3_server.c -o ex3_server`
     21  */
     22 
     23 /* Results to be sent back to the client. */
     24 struct pack_res {
     25 	char str[32];
     26 	float avg;
     27 };
     28 
     29 struct foo {
     30 	int cfd;	/* Client file descriptor. */
     31 	int nsucc;	/* Number of successful calculations (avg > 10). */
     32 	int ntotal;	/* Total number of servings. */
     33 };
     34 
     35 static int srv(struct foo *);
     36 static void sighandler(int);
     37 static void *emalloc(size_t);
     38 
     39 static char *argv0;
     40 /* Gets set only if a termination signal is caught. */
     41 static volatile sig_atomic_t f_quit = 0;
     42 
     43 static int
     44 srv(struct foo *foo)
     45 {
     46 	struct foo *f;
     47 	struct pack_res *res;
     48 	int *arr;
     49 	int i, n, sum;
     50 	int rc;
     51 	char cont;
     52 
     53 	f = (struct foo *)foo;
     54 	/*
     55 	 * The return code is -1 (error) by default so that we don't
     56 	 * need to set it everytime an error occurs in the main loop.
     57 	 * If there are no errors, `rc` will be set to 0 (success) right
     58 	 * after the loop.
     59 	 */
     60 	rc = -1;
     61 	for (;;) {
     62 		if (recv(f->cfd, &n, sizeof(int), 0) < 0)
     63 			goto fail;
     64 		arr = emalloc(n * sizeof(int));
     65 		if (recv(f->cfd, arr, n * sizeof(int), 0) < 0)
     66 			goto fail;
     67 		res = emalloc(sizeof(struct pack_res));
     68 
     69 		printf("cfd: %d\tn: %d\n", f->cfd, n);
     70 		for (i = 0, sum = 0; i < n; i++) {
     71 			printf("cfd: %d\tarr[%d]: %d\n", f->cfd, i, arr[i]);
     72 			sum += arr[i];
     73 		}
     74 		free(arr);
     75 		/* 
     76 		 * When we go to `fail:`, `gcc` for some reason double frees if 
     77 		 * we don't manually set it to NULL, even though we explicitly 
     78 		 * check for NULL first...
     79 		 */
     80 		arr = NULL;
     81 
     82 		if ((res->avg = sum / (float)n) > 10.0f) {
     83 			(void)strncpy(res->str, "sequence: ok",
     84 			    sizeof(res->str) - 1);
     85 			f->nsucc++;
     86 		} else
     87 			(void)strncpy(res->str, "sequence: failed",
     88 			    sizeof(res->str) - 1);
     89 		if (send(f->cfd, res, sizeof(struct pack_res), 0) < 0)
     90 			goto fail;
     91 		f->ntotal++;
     92 		printf("[%s] success: %d: total: %d\n",
     93 		    argv0, f->nsucc, f->ntotal);
     94 
     95 		if (recv(f->cfd, &cont, 1, 0) < 0)
     96 			goto fail;
     97 		if (cont == 'n')
     98 			break;
     99 	}
    100 	rc = 0;
    101 fail:
    102 	printf("[%s] connection with client %d closed\n", argv0, f->cfd);
    103 	(void)close(f->cfd);
    104 	if (arr != NULL)
    105 		free(arr);
    106 	if (res != NULL)
    107 		free(res);
    108 
    109 	return rc;
    110 }
    111 
    112 static void
    113 sighandler(int sig)
    114 {
    115 	f_quit = 1;
    116 }
    117 
    118 static void *
    119 emalloc(size_t nb)
    120 {
    121 	void *p;
    122 
    123 	if ((p = malloc(nb)) == NULL)
    124 		err(1, "malloc");
    125 
    126 	return p;
    127 }
    128 
    129 static void
    130 usage(void)
    131 {
    132 	fprintf(stderr, "usage: %1$s [-b backlog] [-s sockfile]\n"
    133 	    "       %1$s -i [-p port] [-b backlog] [-s sockfile] hostname\n"
    134 	    "       %1$s -i [-p port] [-b backlog] [-s sockfile] ipv4_addr\n", argv0);
    135 	exit(1);
    136 }
    137 
    138 int
    139 main(int argc, char *argv[])
    140 {
    141 	struct foo *f;
    142 	struct sockaddr_un sun;
    143 	struct sockaddr_in sin;
    144 	struct hostent *hp;
    145 	struct sigaction sa;
    146 	char *sockfile = "/tmp/cool.sock";
    147 	int sfd;
    148 	int backlog = 10;
    149 	int port = 9999;
    150 	int iflag, uflag;
    151 	char ch;
    152 
    153 	argv0 = *argv;
    154 	/* Run in the UNIX domain by default. */
    155 	uflag = 1;
    156 	iflag = 0;
    157 
    158 	while ((ch = getopt(argc, argv, "b:ip:s:")) != -1) {
    159 		switch (ch) {
    160 		case 'b':
    161 			/* 
    162 			 * Negative `backlog` value normally requests the
    163 			 * maximum allowable value (HISTORY section of
    164 			 * listen(2)'s FreeBSD man page), but it's better to
    165 			 * not allow it in case the user passes a negative
    166 			 * value accidentally. Also a value of 0 doesn't make
    167 			 * sense, so we don't allow it either.
    168 			 */
    169 			if ((backlog = atoi(optarg)) < 1)
    170 				errx(1, "backlog value must be greater than 1");
    171 			break;
    172 		case 'i':
    173 			/* Run the server on the internet domain. */
    174 			iflag = 1;
    175 			uflag = 0;
    176 			break;
    177 		case 'p':
    178 			/* Choose custom port but don't use a well-known port. */
    179 			if ((port = atoi(optarg)) < 1024)
    180 				errx(1, "can't use port number < 1024");
    181 			break;
    182 		case 's':
    183 			sockfile = optarg;
    184 			break;
    185 		case '?':
    186 		default:
    187 			usage();
    188 		}
    189 	}
    190 	argc -= optind;
    191 	argv += optind;
    192 
    193 	/* 
    194 	 * If we're on the internet domain, we also need a hostname 
    195 	 * or an IPv4 address. 
    196 	 */
    197 	if (iflag && argc < 1)
    198 		usage();
    199 	/*
    200 	 * Handle termination signals so we don't exit abnormally
    201 	 * (i.e without cleaning up resources).
    202 	 */
    203 	(void)memset(&sa, 0, sizeof(sa));
    204 	(void)sigfillset(&sa.sa_mask);
    205 	sa.sa_handler = sighandler;
    206 	if (sigaction(SIGINT, &sa, NULL) < 0)
    207 		err(1, "sigaction(SIGINT)");
    208 	if (sigaction(SIGTERM, &sa, NULL) < 0)
    209 		err(1, "sigaction(SIGTERM)");
    210 
    211 	/* Set the socket up for use in the Internet domain. */
    212 	if (iflag) {
    213 		if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    214 			err(1, "socket(AF_INET)");
    215 		(void)memset(&sin, 0, sizeof(sin));
    216 		sin.sin_family = AF_INET;
    217 		/* Convert the port number to network bytes. */
    218 		sin.sin_port = htons(port);
    219 
    220 		/* 
    221 		 * We'll try and see if the input is an IPv4 address. If
    222 		 * inet_aton(3) does not fail, the user did pass an IPv4 address.
    223 		 * However if inet_addr(3) fails, we'll assume the user passed
    224 		 * a hostname. If the hostname was wrong, gethostbyname(3) will
    225 		 * fail. That lets us use both hostnames and IPv4 addresses
    226 		 * as arguments.
    227 		 */
    228 		if (!inet_aton(*argv, &sin.sin_addr)) {
    229 			/* 
    230 			 * Get host info by hostname. The host's IPv4 address
    231 			 * will be written to `hp->h_addr`.
    232 			 */
    233 			if ((hp = gethostbyname(*argv)) == NULL)
    234 				errx(1, "gethostbyname(%s) failed", *argv);
    235 			(void)memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
    236 		}
    237 		if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    238 			err(1, "bind");
    239 
    240 		printf("Socket: %s\nDomain: AF_INET\nIPv4: %s\n"
    241 		    "Port: %d\nBacklog: %d\n", 
    242 		    sockfile, inet_ntoa(sin.sin_addr), port, backlog);
    243 	/* Set the socket up for use in the UNIX domain. */
    244 	} else if (uflag) {
    245 		if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    246 			err(1, "socket(AF_UNIX)");
    247 		(void)memset(&sun, 0, sizeof(sun));
    248 		sun.sun_family = AF_UNIX;
    249 		(void)strncpy(sun.sun_path, sockfile, sizeof(sun.sun_path) - 1);
    250 		if (bind(sfd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
    251 			err(1, "bind");
    252 
    253 		printf("Socket: %s\nDomain: AF_UNIX\nBacklog: %d\n",
    254 		    sockfile, backlog);
    255 	}
    256 	if (listen(sfd, backlog) < 0)
    257 		err(1, "listen");
    258 
    259 	f = emalloc(sizeof(struct foo));
    260 	f->nsucc = 0;
    261 	f->ntotal = 0;
    262 
    263 	for (;;) {
    264 		/* We caught a termination signal. */
    265 		if (f_quit)
    266 			break;
    267 		/*
    268 		 * accept(2)'s `addr` and `addrlen` arguments can be NULL if
    269 		 * we don't care about the address information of the client.
    270 		 */
    271 		if ((f->cfd = accept(sfd, NULL, NULL)) < 0)
    272 			continue;
    273 		printf("[%s] accepted client: %d\n", argv0, f->cfd);
    274 		/* 
    275 		 * Create a child process to serve the client so the parent can
    276 		 * continue waiting for another client to serve.
    277 		 */
    278 		switch (fork()) {
    279 		case -1:
    280 			err(1, "fork");
    281 		case 0:
    282 			if (srv(f) < 0)
    283 				warnx("srv failed");
    284 			_exit(0);
    285 		default:
    286 			(void)close(f->cfd);
    287 		}
    288 
    289 	}
    290 	/* Will get here only if a termination signal is caught. */
    291 	(void)close(f->cfd);
    292 	(void)close(sfd);
    293 	(void)unlink(sockfile);
    294 	free(f);
    295 
    296 	return 0;
    297 }