[Computernetzwerk] Interpretation der Socket-API der Netzwerkprogrammierschnittstelle (3)

  Socket ist eine API, die Programmierern über den Netzwerkprotokollstapel zur Verfügung gestellt wird. Im Vergleich zu komplexen Computernetzwerkprotokollen abstrahiert die API wichtige Vorgänge und Konfigurationsdaten und vereinfacht so die Programmprogrammierung.

        Der in diesem Artikel beschriebene Socket-Inhalt stammt vom Man-Tool der Linux-Distribution Centos 9 und es wird einige Unterschiede zwischen anderen Plattformen (z. B. OS-X und verschiedenen Versionen) geben. In diesem Artikel wird hauptsächlich jede API im Detail vorgestellt, um die Socket-Programmierung besser zu verstehen.


Umfrage

poll() entspricht POSIX.1 - 2008

ppoll() folgt Linux

1.Bibliothek

标准 c 库,libc, -lc

2.Header-Datei

<poll.h>

3. Schnittstellendefinition

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);
       int ppoll(struct pollfd *fds, nfds_t nfds,
                 const struct timespec *_Nullable tmo_p,
                 const sigset_t *_Nullable sigmask);

4. Schnittstellenbeschreibung

        poll() macht dasselbe wie select(), es wartet darauf, dass eine Reihe von Dateideskriptoren für E/A bereit sind. epoll() von Linux ist ähnlich, bietet jedoch einige mehr Funktionen als poll().

        Der fds-Parameter ist der zu überwachende Dateideskriptorsatz, der ein Array mit der folgenden Struktur ist:

           struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

        Die Anzahl der Einträge in fds wird vom Aufrufer vorgegeben.

        fd in der Struktur enthält einen offenen Dateideskriptor. Wenn es sich um einen negativen Wert handelt, wird der Ereignisparameter ignoriert und revents gibt 0 zurück. (Das heißt, Sie können fd auf sein Komplement setzen und es ignorieren).

        Der Ereignisparameter ist ein Eingabeparameter, bei dem es sich um eine bitweise Maske handelt, die Ereignisse in Dateideskriptoren identifiziert, die für die Anwendung von Interesse sind. Der Parameter kann auf 0 gesetzt werden, dann können nur POLLHUP/POLLERR/POLLNVAL-Ereignisse zurückgegeben werden.

        revents ist ein Ausgabeparameter, der vom Kernel mit den tatsächlich aufgetretenen Ereignissen gefüllt wird. Diese Ereignisse können die in Ereignisse angegebenen Ereignisse oder eines von POLLHUP/POLLERR/POLLNVAL sein. (Die Bits, die diesen drei Ereignissen in Ereignissen entsprechen, haben keine Bedeutung. Solange die entsprechenden Bedingungen erfüllt sind, geben Revents das Ereignis zurück.)

        Wenn kein angefordertes Ereignis (einschließlich Fehler) auftritt, blockiert poll(), bis ein Ereignis eintritt.

        Der Timeout-Parameter gibt die Anzahl der Millisekunden an, die poll() wartet, bis der Dateideskriptor bereit ist. Der Aufruf wird blockiert, bis:

  • Dateideskriptor bereit
  • Der Anruf wurde durch ein Signal unterbrochen
  • Es ist eine Zeitüberschreitung aufgetreten

        Ebenso wird der Timeout-Wert ebenfalls an die Granularität der Systemuhr angenähert, und es können aufgrund von Verzögerungen bei der Kernel-Planung etwas mehr Ereignisse blockiert werden. Wenn Timeout ein negativer Wert ist, bedeutet dies, dass das Timeout unendlich ist. Wenn timeout auf 0 gesetzt ist, kehrt poll() sofort zurück, auch wenn keine Dateideskriptoren bereit sind.

        Jedes Bit in Ereignissen und Ereignissen ist in poll.h definiert:

    POLLIN

       Es liegen Daten zum Lesen vor.

    UMFRAGEPREIS

        Im Dateideskriptor tritt eine Ausnahme auf, die folgende sein kann: (1) Es liegen Out-of-Band-Daten auf dem TCP-Socket vor (2) Der Pseudo-Terminal-Host hat im Nachrichtenmodus eine Slave-Statusänderung entdeckt (3) die cgroup.events Datei wurde geändert.

        UMFRAGE

       Derzeit beschreibbar, aber das Schreiben von Daten, die größer sind als der verfügbare Platz im Socket oder in der Pipe, führt immer noch zu Blockierungen (es sei denn, O_NONBLOCK ist festgelegt).

        POLLRDHUP

        Der Streaming-Socket-Peer hat die Verbindung geschlossen oder heruntergefahren, während er eine Halbverbindung geschrieben hat. Diese Definition hängt vom Makro _GNU_SOURCE ab.

        POLLER

       Ein Fehler ist aufgetreten. Wenn der Dateideskriptor auf das Schreibende der Pipe zeigt und das Leseende geschlossen ist, wird dieser Fehler ebenfalls zurückgegeben.

        POLLHUP

        auflegen. Beim Lesen einer Pipe oder eines Stream-Sockets bedeutet dieses Ereignis nur, dass das Peer-Ende seinen Kanal geschlossen hat. Wenn beim Lesen nachfolgender Daten die Daten im Kanal gelesen und dann weiter gelesen werden, wird 0 (EOF) zurückgegeben.

        POLLNVAL

        Die Anfrage ist illegal: fd ist nicht geöffnet.

        Beim Kompilieren mit dem Makro _XOPEN_SOURCE kommt es außerdem zu folgenden Ereignissen, die jedoch nicht viele Informationen liefern:

        POLLRD-NORM

        Entspricht POLLIN.

        POLLRDBAND

        Prioritätsbandbreitendaten können gelesen werden (wird normalerweise unter Linux verwendet)

        POLLWRNORM

        Entspricht POLLOUT

        POLLWRAND

        Es können Prioritätsdaten geschrieben werden

ppoll()

        Die Beziehung zwischen ppoll() und poll() ähnelt der Beziehung zwischen select() und pselect(). ppoll() bietet Anwendungen eine sichere Möglichkeit, auf Signale oder Bereitschaftsereignisse zu warten.

        Abgesehen vom Unterschied in der Genauigkeit der Timeout-Zeit sind die folgenden beiden Codeteile nahezu gleichwertig

           ready = ppoll(&fds, nfds, tmo_p, &sigmask);
           sigset_t origmask;
           int timeout;

           timeout = (tmo_p == NULL) ? -1 :
                     (tmo_p->tv_sec * 1000 + tmo_p->tv_nsec / 1000000);
           pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
           ready = poll(&fds, nfds, timeout);
           pthread_sigmask(SIG_SETMASK, &origmask, NULL);

          Der obige Code gilt eher als nahezu gleichwertig als gleichwertig, hauptsächlich weil der negative Wert von timeout von poll() als ewiges Warten interpretiert wird und der negative Wert von *tmo_p in ppoll() einen Fehler meldet.

        Sie können pselect(2) lesen, um zu sehen, warum ppoll notwendig ist.

        Wenn der Sigma-Parameter NULL ist, erfolgt keine Signalmaskierungsoperation. Derzeit besteht der einzige Unterschied zwischen den beiden Schnittstellen in der Zeitgenauigkeit.

        tmo_p gibt die Obergrenze der Zeit an, die ppoll() blockiert. Es ist ein Zeiger auf die Zeitspezifikationsstruktur. Wenn der Zeiger leer ist, blockiert ppoll() immer.

5. Rückgabewert

        Bei Erfolg gibt poll() eine nicht negative Zahl zurück, die angibt, wie viele Dateideskriptoren in pollfds Ereignisse haben, d. h. die entsprechenden Ereignisse wurden auf einen Wert ungleich Null aktualisiert. Die Rückgabe von 0 zeigt an, dass keine Dateideskriptoren bereit waren und eine Zeitüberschreitung aufgetreten ist.

        Wenn ein Fehler auftritt, wird -1 zurückgegeben und errno wird gesetzt, um die Art des Fehlers anzugeben.

        Der Fehlerwert ist wie folgt definiert:

EFAULT fds zeigt auf einen Adressraum außerhalb des Prozesses
EINTR Bevor das Anforderungsereignis eintritt, tritt ein Signal auf. Einzelheiten finden Sie unter signal(7).
EIVAL Der nfds-Wert überschreitet den RLIMIT_NOFILE-Grenzwert
EINZIGE WAHL *tmo_P in ppoll() ist ein unzulässiger Wert (negative Zahl)
ENOMEM Nicht genügend Speicher zum Zuweisen von Kernel-Datenstrukturen

Auf einigen anderen UNIX-Systemen kann poll() im Gegensatz zu ENOMEM unter Linux einen Fehler vom Typ EAGAIN generieren, wenn der Kernel keine Kernelressourcen zuweist. POSIX lässt dieses Verhalten zu. Daher muss ein portables Programm diesen Fehler erkennen und es erneut versuchen, genau wie EINTR.

Einige Implementierungen definieren die nicht standardmäßige Konstante INFTIM (-1), die als Timeout von poll() verwendet wird, aber diese Konstante wird von glibc nicht bereitgestellt.

6. Achtung

       Das Verhalten von poll() und ppoll() wird durch das O_NONBLOCK-Flag nicht beeinflusst.

        Eine Diskussion der Situation, in der ein Dateideskriptor von poll() abgehört, aber von einem anderen Thread geschlossen wird, finden Sie unter select(2).

7.Fehler

        Bitte beachten Sie die Diskussion über falsche Bereitschaftsbenachrichtigungen in select(2). 

8.Codebeispiele

        Das Programm öffnet den vom Befehlszeilenparameter übergebenen Dateinamen und hört auf sein POLLIN-Ereignis. Das Programm ruft poll() zyklisch auf, um den Dateideskriptor zu überwachen und die Anzahl der bereiten Dateideskriptoren auszugeben. Für jeden fertigen Dateideskriptor führt das Programm Folgendes aus:

  • Zeigen Sie zurückgegebene Ereignisse in einem für Menschen lesbaren Format an
  • Wenn der Dateideskriptor fertig ist, lesen Sie einige Daten daraus und drucken Sie ihn aus
  • Wenn der Dateideskriptor nicht lesbar ist, aber ein anderes Ereignis auftritt (z. B. POLLHUP), schließen Sie den Dateideskriptor

        Angenommen, wir führen das Programm in einem Terminal aus und bitten es, ein FIFO zu öffnen:

       $ mkfifo myfifo
       $ ./poll_input myfifo

        Öffnen Sie den FIFO in einem anderen Terminal, schreiben Sie einige Daten und schließen Sie dann den FIFO:

       $ echo aaaaabbbbbccccc > myfifo

                 Auf dem Terminal, auf dem das Programm ausgeführt wird, werden die folgenden Informationen angezeigt:

           Opened "myfifo" on fd 3
           About to poll()
           Ready: 1
             fd=3; events: POLLIN POLLHUP
               read 10 bytes: aaaaabbbbb
           About to poll()
           Ready: 1
             fd=3; events: POLLIN POLLHUP
               read 6 bytes: ccccc

           About to poll()
           Ready: 1
             fd=3; events: POLLHUP
               closing fd 3
           All file descriptors closed; bye

         Aus dem Obigen können wir ersehen, dass poll() dreimal zurückkehrt:

  • Die erste Rückgabe ist POLLIN, was angibt, dass der Dateideskriptor lesbar ist, und die andere ist POLLHUP, was angibt, dass das andere Ende des Dateideskriptors geschlossen ist. Das Programm liest dann einige der verfügbaren Eingabedaten
  • Bei der zweiten Rückgabe handelt es sich um dieselben beiden Ereignisse, die jedoch immer noch einige verfügbare Daten verbrauchen.
  • Bei der letzten Rückkehr von poll() gibt es nur ein POLLHUP-Ereignis, dann wird der Dateideskriptor geschlossen und das Programm beendet.
       /* poll_input.c

          Licensed under GNU General Public License v2 or later.
       */
       #include <fcntl.h>
       #include <poll.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>

       #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)

       int
       main(int argc, char *argv[])
       {
           int            ready;
           char           buf[10];
           nfds_t         num_open_fds, nfds;
           ssize_t        s;
           struct pollfd  *pfds;

           if (argc < 2) {
              fprintf(stderr, "Usage: %s file...\n", argv[0]);
              exit(EXIT_FAILURE);
           }

           num_open_fds = nfds = argc - 1;
           pfds = calloc(nfds, sizeof(struct pollfd));
           if (pfds == NULL)
               errExit("malloc");

           /* Open each file on command line, and add it to 'pfds' array. */

           for (nfds_t j = 0; j < nfds; j++) {
               pfds[j].fd = open(argv[j + 1], O_RDONLY);
               if (pfds[j].fd == -1)
                   errExit("open");

               printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);

               pfds[j].events = POLLIN;
           }

           /* Keep calling poll() as long as at least one file descriptor is
              open. */

           while (num_open_fds > 0) {
               printf("About to poll()\n");
               ready = poll(pfds, nfds, -1);
               if (ready == -1)
                   errExit("poll");

               printf("Ready: %d\n", ready);

               /* Deal with array returned by poll(). */

               for (nfds_t j = 0; j < nfds; j++) {
                   if (pfds[j].revents != 0) {
                       printf("  fd=%d; events: %s%s%s\n", pfds[j].fd,
                              (pfds[j].revents & POLLIN)  ? "POLLIN "  : "",
                              (pfds[j].revents & POLLHUP) ? "POLLHUP " : "",
                              (pfds[j].revents & POLLERR) ? "POLLERR " : "");

                       if (pfds[j].revents & POLLIN) {
                           s = read(pfds[j].fd, buf, sizeof(buf));
                           if (s == -1)
                               errExit("read");
                           printf("    read %zd bytes: %.*s\n",
                                  s, (int) s, buf);
                       } else {                /* POLLERR | POLLHUP */
                           printf("    closing fd %d\n", pfds[j].fd);
                           if (close(pfds[j].fd) == -1)
                               errExit("close");
                           num_open_fds--;
                       }
                   }
               }
           }

           printf("All file descriptors closed; bye\n");
           exit(EXIT_SUCCESS);
       }

Supongo que te gusta

Origin blog.csdn.net/BillyThe/article/details/132774658
Recomendado
Clasificación