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