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);
}
}
}