dial on demand

Giorgos Keramidas keramida at ceid.upatras.gr
Tue Nov 29 08:35:27 EET 2005


On 2005-11-28 21:12, nikos roussos <nikos at hellug.gr> wrote:
> ok, αυτά είναι τα includes:
>
> #include <stdio.h>
> #include <string.h>
> #include <stdlib.h>
> #include <arpa/inet.h>
> #include <sys/socket.h>
> #include <netinet/in.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> #include <sys/wait.h>
> #include <signal.h>
>
> με τα παραπάνω κάνει κανονικά compile.

Μόνο που είναι λάθος η σειρά τους και ακόμα έχει warnings:

    foo.c: In function `main':
    foo.c:45: warning: implicit declaration of function `fork'
    foo.c:45: warning: nested extern declaration of `fork'
    foo.c:46: warning: implicit declaration of function `close'
    foo.c:46: warning: nested extern declaration of `close'
    foo.c:47: warning: implicit declaration of function `read'
    foo.c:47: warning: nested extern declaration of `read'
    foo.c:55: warning: nested extern declaration of `close'
    foo.c:46: warning: redundant redeclaration of 'close'
    foo.c:46: warning: previous implicit declaration of 'close' was here
    foo.c: In function `signal_child':
    foo.c:62: warning: declaration of 'stat' shadows a global declaration
    /usr/include/sys/stat.h:331: warning: shadowed declaration is here
    foo.c: At top level:
    foo.c:60: warning: unused parameter 'signo'
    *** Error code 1

Τα προβλήματα που έχει το πρόγραμμα είναι ακόμα χειρότερα αν το διαβάσει
κανείς γραμμή προς γραμμή:

     1  #include <stdio.h>
     2  #include <string.h>
     3  #include <stdlib.h>
     4  #include <arpa/inet.h>
     5  #include <sys/socket.h>
     6  #include <netinet/in.h>
     7  #include <sys/types.h>
     8  #include <sys/stat.h>
     9  #include <sys/wait.h>
    10  #include <signal.h>

Η σειρά αυτή είναι λάθος.  Η ``σωστή'' σειρά των headers είναι τις
περισσότερες φορές κάτι σαν:

            #include <sys/types.h>		/* Πρώτο πάντα το sys/types.h */

	    /*
	     * Τα άλλα sys/*.h headers, ταξινομημένα αλφαβητικά, εκτός
	     * κι αν υπάρχει πολύ καλός λόγος για να μην είναι
	     * ταξινομημένα.
	     */
            #include <sys/socket.h>
            #include <sys/stat.h>

	    /*
	     * Τα headers από άλλους υποκαταλόγους του /usr/include
	     * (ή κάποιου άλλου καταλόγου του include path).
	     */
            #include <arpa/inet.h>
            #include <netinet/in.h>

	    /*
	     * Όλα τα άλλα headers, ταξινομημένα κι αυτά.
	     */
            #include <signal.h>
            #include <stdio.h>
            #include <stdlib.h>
            #include <string.h>

    12  #define BUFSIZE 1024

Δεν υπάρχει κανείς λόγος να περιορίζεσαι σε 1024 bytes ανά γραμμή, οπότε
καλύτερα είναι να βγάλεις τέτοιους αυθαίρετους στατικούς περιορισμούς
από το πρόγραμμα.  Μπορείς πάντα να χρησιμοποιήσεις κάτι σαν:

    #include <sys/types.h>

    #include <sys/uio.h>

    #include <assert.h>
    #include <stdlib.h>
    #include <unistd.h>

    static char *appendline(char **_line, size_t *_len, size_t *_sz, int _ch);
    char *getline(int fd);

    char *
    getline(int fd)
    {
        unsigned char ch;
        char *line, *tmpline;
        size_t linelen, linesz;

        assert(fd >= 0);

        line = NULL;
        linelen = linesz = 0;
        for (;;) {
            ch = 0;
            if (read(fd, &ch, 1) != 1) {
	        free(line);
		return NULL;
            }
            if ((tmpline = appendline(&line, &linelen, &linesz, ch)) == NULL) {
                free(line);
                return NULL;
            } else {
                line = tmpline;
            }
            if (ch == '\n')
	        break;
        }
        if ((tmpline = appendline(&line, &linelen, &linesz, '\0')) == NULL) {
            free(line);
            return NULL;
        }
        if ((tmpline = realloc(line, linelen + 1)) != NULL)
            line = tmpline;
        return line;
    }

    #define LINE_GROWSIZE 100

    static char *
    appendline(char **line, size_t *linelen, size_t *linesz, int ch)
    {
        char *tmpline;

        assert(line != NULL);
	assert((*line == NULL && *linesz == 0) ||
	       (*line != NULL && *linesz != 0));
	assert(ch >= 0);
        while (*linelen >= *linesz) {
            tmpline = realloc(*line, (*linesz + LINE_GROWSIZE));
            if (tmpline == NULL)
                return NULL;
            *line = tmpline;
            *linesz += LINE_GROWSIZE;
        }
        (*line)[(*linelen)++] = ch;
        return (*line);
    }

Ετσι είσαι σίγουρος ότι όσο μεγάλη και να είναι η γραμμή εισόδου το
πρόγραμμά σου δε θα έχει πρόβλημα να κάνει κάτι σαν αυτό:

    char *line;
    int sockfd;

    while ((line = getline(sockfd)) != NULL) {
        parse(line);
        free(line);
    }

    14  void signal_child(int signo);
    15
    16  int
    17  main(void)
    18  {
    19          pid_t child_pid;
    20          int sock_fd, sock_in;
    21          struct sockaddr_in sock_srv;
    22          socklen_t sock_len;
    23          int srv_port = 9999;

Οι 'μαγικοί' αριθμοί είναι λίγο απαίσιοι, αλλά εντάξει δεν είναι τόσο
τραγικό αυτό.

    24          int data_len;
    25          char buf[BUFSIZE];
    26
    27          if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    28                  perror("Error: socket");

Δεν αρκεί το perror().  Αν αποτύχει η socket(), το πρόγραμμά σου θα
συνεχίσει να τρέχει, και κάποια στιγμή (π.χ. στη γραμμή 35 παρακάτω),
θα προσπαθήσει να χρησιμοποιήσει ένα άκυρο file descriptor.

    30          memset(&sock_srv, 0, sizeof(sock_srv));
    31          sock_srv.sin_family = AF_INET;
    32          sock_srv.sin_port = htons(srv_port);
    33
    34          sock_len = sizeof(sock_srv);
    35          if ((bind(sock_fd, (struct sockaddr *)&sock_srv, sock_len)) < 0)
    36                  perror("Error: bind");

Δεν αρχικοποιείς το πεδίο sock_srv.sin_addr.s_addr σε καμία γνωστή τιμή.
Γενικά ίσως να είσαι τυχερός και να παίζει αυτό εκεί που τυχαίνει να
ισχύει ότι (INADDR_ANY == 0).  Δεν είναι καλή ιδέα όμως.

    38          if ((listen(sock_fd, 5)) < 0)
    39                  perror("Error, listen");

Τα ίδια με την bind() σε περίπτωση αποτυχίας.

    41          signal(SIGCHLD, signal_child);

Δεν ελέγχεις αν πέτυχε η εγκατάσταση του δικού σου signal handler.
Καλύτερα κάτι σαν αυτό:

    if (signal(SIGCHLD, signal_child) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

Ενα άλλο, σχετικά αθώο, λάθος είναι ότι οι signal handlers
κληροδοτούνται και στα child processes, οπότε θα 'πρεπε κανονικά να
χρησιμοποιείς και το SIG_IGN στο child process.  Ευτυχώς αυτό δεν είναι
τόσο 'επικίνδυνο', αφού μόνο το parent process θα πάρει SIG_CHLD λόγω
του δικού σου κώδικα.  Προσοχή όμως παρακάτω στα σχόλια για την
system()...

    43          while (1) {
    44                  if ((sock_in = accept(sock_fd, (struct sockaddr *)&sock_srv, &sock_len)) < 0)
    45                          perror("Error: accept");

Τα ίδια με την bind() και listen(), σε περίπτωση αποτυχίας της accept().

    47                  if ((child_pid = fork()) == 0) {
    48                          close(sock_fd);
    49                          while ((data_len = read(sock_in, buf, sizeof(buf))) > 0) {
    50                                  if (strspn(buf, "con") > 2)
    51                                          system("ppp-go");
    52                                  if (strspn(buf, "discon") > 5)
    53                                          system("eznet down");
    54                          }

Δεν είναι και πολύ καλός τρόπος αυτός να διαβάσεις 'μια γραμμή' από το
socket.  Δες παραπάνω, για την getline() ή κάτι αντίστοιχο.

Το πιο 'περίεργο' κομμάτι εδώ είναι ότι μπορεί το signal_child() handler
να κληθεί επειδή η system() έκανε fork() & execve() κάποιο πρόγραμμα, το
οποίο πρόγραμμα τερμάτισε.  Μάλλον δε θες να χρησιμοποιηθεί το δικό σου
signal handler εδώ, οπότε καλύτερα είναι στο child process να
απεγκαθιστάς το signal_child() handler (αφήνοντας τη system() να κάνει
ότι αυτή νομίζει καλύτερο).

    55                          exit(0);
    56                  }

ΠΑΝΤΟΥ όπου χρησιμοποιείται η fork() είναι μεγάλο λάθος να καλείς την
exit().  Αυτή κάνει και πράγματα που μπορεί να επηρεάσουν το parent
process.  Η σωστή συνάρτηση σε child processes είναι ΠΑΝΤΑ η _exit().

    57                  close(sock_in);
    58          }
    59          exit(0);
    60  }

Αυτό το κομμάτι δεν εκτελείται ποτέ.

    62  void
    63  signal_child(int signo)
    64  {
    65          pid_t pid;
    66          int stat;
    67
    68          while ((pid = waitpid(-1, &stat, WNOHANG)) > 0);
    69          return;
    70  }

Σχετικά εντάξει...




More information about the Linux-greek-users mailing list