Zkouška 4.2.2016 - TCP server s měřením času příkazů

Programování v UNIXu. Cvičení probíhá v laboratoři UNIX a poskytuje posluchačům průpravu v programování v jazyce C v prostředí UNIX.
Jenda_

Zkouška 4.2.2016 - TCP server s měřením času příkazů

Příspěvek od Jenda_ »

Už to tu někde bylo v mírně těžší variantě (kdy příkaz mohl mít parametry).

Napište TCP server.

Klient se připojí a zadá příkaz (jméno programu, bez parametrů). Server příkaz spustí, změří, jak dlouho běžel, pošle tuto informaci zpátky a spojení ukončí. V případě, že příkaz nedoběhne do nastaveného timeoutu, server jej zabije.

Důležité je zabíjet celou process grupu (kterou si musíte vytvořit), protože jinak tam zůstanou viset zaseknuté děti.

Řešení bylo možné forkem na každého klienta nebo přes select.

Kód: Vybrat vše

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <signal.h>

#include <time.h>

#include <sys/wait.h>

#define	nclients 5
#define	rsize 64

typedef struct cl {
	int free;
	pid_t pid;
} cl;

int timeout = 5;
int clientfd = -1;
unsigned datestart = 0;

int sfd;

cl clients[nclients];

pid_t grandchild;

// write N bytes to fd
void writen(int fd, char * buf, int n) {
	int pos = 0;
	while (pos < n) {
		ssize_t j = write(fd, buf+pos, n-pos);
		if (j <= 0) {
			fprintf(stderr, "error at %i
", pos);
			exit(1);
		}
		pos += j;
	}
}

void printdelay() {
#define	mlen (sizeof (unsigned) + 10)
	char * msg = malloc(mlen);
	unsigned ctime = (unsigned)time(NULL);

	int rlen = snprintf(msg, mlen, "%i sec
", (ctime-datestart));
	printf("signalling: '%s'
", msg);
	writen(clientfd, msg, rlen);
	shutdown(clientfd, SHUT_RDWR);
	close(clientfd);
	exit(0);
}

void child_handler(int sig) {
	printf("alarm/int handler (%i), killing %i
", sig, -grandchild);

	pid_t pgroup = getpgid(grandchild);

	kill(-pgroup, SIGTERM);

	while (waitpid(-1, NULL, WNOHANG) > 0) {
	}

	shutdown(clientfd, SHUT_RDWR);
	close(clientfd);
	exit(2);
}

void parent_handler(int sig) {
	printf("Got INT
");

	// kill all our children
	for (int i = 0; i < nclients; i++) {
		if (!clients[i].free) {
			kill(clients[i].pid, SIGINT);
		}
	}

	// grab generated zombies
	while (waitpid(-1, NULL, WNOHANG) > 0) {
		printf("killing zombie
");
	}

	shutdown(sfd, SHUT_RDWR);
	close(sfd);

	// die
	exit(0);
}

int main(int argc, char *argv[]) {

	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int s;

	if (argc != 3) {
		fprintf(stderr, "Usage: %s port timeout
", argv[0]);
		exit(EXIT_FAILURE);
	}

	timeout = atoi(argv[2]);

	memset(&hints, 0, sizeof (struct addrinfo));
	hints.ai_family = AF_UNSPEC;	/* Allow IPv4 or IPv6 */
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_protocol = 0;		/* Any protocol */
	hints.ai_canonname = NULL;
	hints.ai_addr = NULL;
	hints.ai_next = NULL;

	s = getaddrinfo(NULL, argv[1], &hints, &result);
	// s = getaddrinfo("localhost", argv[1], &hints, &result);
	if (s != 0) {
		fprintf(stderr, "getaddrinfo: %s
", gai_strerror(s));
		exit(EXIT_FAILURE);
	}

	/*  getaddrinfo() returns a list of address structures.
	    Try each address until we successfully bind(2).
	    If socket(2) (or bind(2)) fails, we (close the socket
	    and) try the next address. */

	for (rp = result; rp != NULL; rp = rp->ai_next) {

		if (rp->ai_family == AF_INET6) {
			char * buf = malloc(INET6_ADDRSTRLEN);
			struct sockaddr_in * saddr = (struct sockaddr_in *)
			    rp->ai_addr;
			if (inet_ntop(AF_INET6, &(saddr->sin_addr), buf,
			    INET6_ADDRSTRLEN)) {
				printf("Trying (v6) %s
", buf);
			} else {
				printf("ntop err
");
			}
			free(buf);
		} else if (rp->ai_family == AF_INET) {
			char * buf = malloc(INET_ADDRSTRLEN);
			struct sockaddr_in * saddr = (struct sockaddr_in *)
			    rp->ai_addr;
			if (inet_ntop(AF_INET, & (saddr->sin_addr), buf,
			    INET_ADDRSTRLEN)) {
				printf("Trying (v4) %s
", buf);
			} else {
				printf("ntop err
");
			}
			free(buf);
		}

		// see https://sourceware.org/bugzilla/show_bug.cgi?id=9981
		if (rp->ai_family == AF_INET) {
			// they don't have the fix in Debian yet
			// just remove it if you expect sane behavior of
			// getaddrinfo
			continue;
		}

		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

		int enable = 1;
		if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable,
		    sizeof (int)) < 0) {
			perror("setsockopt(SO_REUSEADDR) failed");
		}

		if (sfd == -1) {
			continue;
		}

		if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {
			printf(" ...bound OK
");
			break;
		}

		close(sfd);
	}

	if (rp == NULL) {	/* No address succeeded */
		fprintf(stderr, "Could not bind
");
		exit(EXIT_FAILURE);
	}

	freeaddrinfo(result);	/* No longer needed */

	// prepare client pid array
	for (int i = 0; i < nclients; i++) {
		clients[i].free = 1;
	}

	// register handler to kill children on INT
	struct sigaction act;
	act.sa_handler = parent_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);

	if (listen(sfd, nclients) == -1) {
		perror("cannot listen
");
	}

	// now we are listening for connections

	while (1) {
		int newsockfd = accept(sfd, NULL, NULL);
		if (newsockfd < 0) {
			perror("cannot accept");
		}

		printf("New connection
");

		// get out of zombies
		pid_t died;
		int status;
		while ((died = waitpid(-1, &status, WNOHANG))) {
			if (died <= 0) {
				break;
			}
			for (int i = 0; i < nclients; i++) {
				if (clients[i].pid == died) {
					printf("finished %i
", died);
					clients[i].free = 1;
				}
			}
		}

		int ret = -1;
		for (int i = 0; i < nclients; i++) {
			if (clients[i].free) {
				ret = i;
			}
		}
		if (ret == -1) {
			printf("No free client
");
			close(newsockfd);
		}

		pid_t child;
		child = fork();

		if (child < 0) {
			perror("cannot fork");
		}

		if (child == 0) { // child

			// start a new process group
			//setpgid(getpid(), getpid());
			setsid();

			// read the request
			char * request = (char *)
			    calloc(1, rsize * sizeof (char));
			int i;
			for (i = 0; i < rsize; i++) {
				ssize_t j = read(newsockfd, request + i, 1);
				if (j <= 0 || request[i] == '
') {
					break;
				}
			}

			if (i >= rsize) {
				char ble[] = "Request too long
";
				writen(newsockfd, ble, sizeof (ble));
				close(newsockfd);
				exit(2);
			}

			printf("Going to exec: '%s'
", request);

			grandchild = fork();

			if (grandchild < 0) {
				perror("cannot fork grandchild");
			}

			if (grandchild == 0) { // grandchild
				execl(request, request, NULL);
				printf("exec fail
");
				close(newsockfd);
				exit(3);
				// not reached
			}

			clientfd = newsockfd;
			datestart = (unsigned)time(NULL);

			// parent
			// register sighandler, set alarm
			struct sigaction act;
			act.sa_handler = child_handler;
			sigemptyset(&act.sa_mask);
			act.sa_flags = 0;
			sigaction(SIGALRM, &act, NULL);
			sigaction(SIGINT, &act, NULL);
			alarm(timeout);

			// wait
			wait(&status);
			alarm(0);
			printdelay();
		} else {
			clients[ret].free = 0;
			clients[ret].pid = child;
			unsigned curtime = time(NULL);
			unsigned to = curtime+timeout;
			printf("forked %i, expire %i
", child, to);
			close(newsockfd);
		}

	}
}
Odpovědět

Zpět na „SWI015 Programování v Unixu“