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.

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

Příspěvekod Jenda_ » 4. 2. 2016 04:10

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

   }
}
Jenda_
 

Zpět na SWI015 Programování v Unixu

Kdo je online

Uživatelé procházející toto fórum: Žádní registrovaní uživatelé a 1 návštěvník