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 }