[Компьютерная сеть] Интерпретация API сокета интерфейса сетевого программирования (3)

  Socket — это API, предоставляемый программистам стеком сетевых протоколов. По сравнению со сложными протоколами компьютерных сетей API абстрагирует ключевые операции и данные конфигурации, упрощая программирование.

        Содержимое сокета, описанное в этой статье, взято из инструмента man из дистрибутива Linux centos 9, и между другими платформами (например, os-x и другими версиями) будут некоторые различия. В этой статье в основном подробно описывается каждый API, чтобы лучше понять программирование сокетов.


голосование

poll() соответствует POSIX.1 – 2008.

ppoll() следует за Linux

1.Библиотека

标准 c 库,libc, -lc

2. Заголовочный файл

<poll.h>

3. Определение интерфейса

       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.Описание интерфейса

        poll() делает то же самое, что и select(): он ждет, пока набор файловых дескрипторов будет готов к вводу-выводу. Функция epoll() в Linux аналогична, но предоставляет несколько больше возможностей, чем poll().

        Параметр fds — это набор дескрипторов файлов, подлежащих мониторингу, который представляет собой массив следующей структуры:

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

        Количество записей в fds задается вызывающей стороной.

        fd в структуре содержит дескриптор открытого файла.Если это отрицательное значение, параметр event будет игнорироваться и revents вернет 0. (То есть вы можете установить fd в качестве дополнения и игнорировать его).

        Параметр событий — это входной параметр, который представляет собой побитовую маску, определяющую события в файловых дескрипторах, представляющих интерес для приложения. Параметр можно установить равным 0, тогда будут возвращаться только события POLLHUP/POLLERR/POLLNVAL.

        revents — это выходной параметр, который заполняется ядром фактически произошедшими событиями. Этими событиями могут быть события, указанные в event, или одно из POLLHUP/POLLERR/POLLNVAL. (Биты, соответствующие этим трем событиям в event, не имеют значения. Пока выполняются соответствующие условия, revents возвращает событие.)

        Если запрошенное событие (включая ошибку) не происходит, poll() будет блокироваться до тех пор, пока не произойдет событие.

        Параметр timeout указывает количество миллисекунд, в течение которых poll() ожидает готовности дескриптора файла, и вызов будет блокироваться до тех пор, пока:

  • дескриптор файла готов
  • Звонок был прерван сигналом
  • Произошел тайм-аут

        Аналогично, значение тайм-аута также аппроксимируется в сторону увеличения детализации системных часов, и может быть заблокировано немного больше событий из-за задержек планирования ядра. Если тайм-аут имеет отрицательное значение, это означает, что тайм-аут бесконечен. Если для тайм-аута установлено значение 0, poll() немедленно завершит работу, даже если дескрипторы файлов не готовы.

        Каждый бит событий и событий определяется в poll.h:

    ПОЛЛИН

       Есть данные для чтения.

    ПРИЗ ОПРОСА

        В файловом дескрипторе возникает исключение. Возможно, (1) в сокете TCP имеются внеполосные данные (2) хост псевдотерминала в режиме сообщений обнаружил изменение статуса подчиненного устройства (3) контрольная группа. файл событий был изменен.

        ОПРОС

       В настоящее время запись доступна, но запись данных, размер которых превышает доступное пространство в сокете или канале, по-прежнему будет блокироваться (если не установлено значение O_NONBLOCK).

        ПОЛЛРДХУП

        Узел потокового сокета закрыл соединение или завершил работу во время записи полусоединения. Это определение зависит от макроса _GNU_SOURCE.

        ПОЛЛЕРР

       Произошла ошибка. Если дескриптор файла указывает на конец канала для записи, а конец чтения закрыт, эта ошибка также будет возвращена.

        ПОЛЛУП

        вешать трубку. При чтении сокета канала или потока это событие означает только то, что партнер закрыл свой канал.При чтении последующих данных продолжение чтения после того, как все данные в канале были прочитаны, вернет 0 (EOF).

        ПОЛЛНВАЛ

        Запрос незаконен: fd не открыт.

        При компиляции с макросом _XOPEN_SOURCE также возникают следующие события, но они не предоставляют много информации:

        ПОЛЛРД НОРМ

        Эквивалент ПОЛЛИНА.

        ПОЛЛРДБЭНД

        Можно прочитать данные о приоритетной полосе пропускания (обычно используется в Linux)

        ОПРОС

        Эквивалент ОПРОСА

        ОПРОС

        Приоритетные данные могут быть записаны

пполл()

        Отношения между ppoll() и poll() аналогичны отношениям между select() и pselect(). ppoll() предоставляет приложениям безопасный метод ожидания сигналов или событий готовности.

        За исключением разницы в точности времени ожидания, следующие два фрагмента кода почти эквивалентны.

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

          Приведенный выше код считается почти эквивалентным, но не эквивалентным, главным образом потому, что отрицательное значение таймаута будет интерпретировано как ожидание опросом(), а отрицательное значение *tmo_p в ppoll() сообщит об ошибке.

        Вы можете обратиться к pselect(2), чтобы понять, зачем нужен ppoll.

        Если параметр sigmask равен NULL, никакой операции маскировки сигнала выполняться не будет.В настоящее время единственной разницей между двумя интерфейсами является точность времени.

        tmo_p определяет верхний предел времени, в течение которого ppoll() будет блокироваться. Это указатель на структуру timespec. Когда указатель пуст, ppoll() всегда будет блокироваться.

5. Возвращаемое значение

        В случае успеха poll() возвращает неотрицательное число, указывающее, сколько файловых дескрипторов в pollfds имеют события, то есть соответствующие события были обновлены до значений, отличных от 0. Возврат 0 означает, что ни один файловый дескриптор не был готов и время ожидания истекло.

        При возникновении ошибки возвращается -1 и устанавливается значение errno, указывающее тип ошибки.

        Значение ошибки определяется следующим образом:

ЭФАУЛТ fds указывает на адресное пространство вне процесса
ЭИНТР Прежде чем произойдет событие запроса, подается сигнал.Подробнее см. signal(7).
ЭИВАЛ Значение nfds превышает предел RLIMIT_NOFILE
ОДИН ВЫБОР *tmo_P в ppoll() — недопустимое значение (отрицательное число).
ЭНОМЕМ Недостаточно памяти для размещения структур данных ядра.

В некоторых других системах UNIX, если ядро ​​не может выделить ресурсы ядра, poll() может генерировать ошибку типа EAGAIN, в отличие от ENOMEM в Linux. POSIX допускает такое поведение. Итак, портативная программа должна обнаружить ошибку и повторить попытку, как и EINTR.

Некоторые реализации определяют нестандартную константу INFTIM (-1), которая используется в качестве тайм-аута poll(), но эта константа не предоставляется glibc.

6. Внимание

       На поведение poll() и ppoll() не влияет флаг O_NONBLOCK.

        Для обсуждения ситуации, когда дескриптор файла прослушивается функцией poll(), но закрывается другим потоком, обратитесь к select(2).

7. ОШИБКИ

        Пожалуйста, обратитесь к обсуждению ложных уведомлений о готовности в select(2). 

8.Примеры кода

        Программа откроет имя файла, переданное в качестве параметра командной строки, и прослушивает событие POLLIN. Программа будет вызывать poll() в цикле, чтобы прослушивать файловые дескрипторы и печатать количество готовых файловых дескрипторов. Для каждого готового файлового дескриптора программа:

  • Отображение возвращенных результатов в удобочитаемом формате.
  • Если дескриптор файла готов, прочитайте из него некоторые данные и распечатайте его.
  • Если дескриптор файла недоступен для чтения, но происходит какое-либо другое событие (например, POLLHUP), закройте дескриптор файла.

        Предположим, мы запускаем программу в терминале и просим ее открыть FIFO:

       $ mkfifo myfifo
       $ ./poll_input myfifo

        Откройте FIFO в другом терминале, запишите некоторые данные, затем закройте FIFO:

       $ echo aaaaabbbbbccccc > myfifo

                 На терминале, на котором запущена программа, мы увидим следующую информацию:

           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

         Из приведенного выше мы видим, что poll() возвращает три раза:

  • Первый возврат — POLLIN, указывающий, что дескриптор файла доступен для чтения, а второй — POLLHUP, указывающий, что другой конец дескриптора файла закрыт. Затем программа считывает некоторые доступные входные данные.
  • Второй возврат такой же, как и эти два события, но все еще потребляет некоторые доступные данные.
  • Последний возврат, poll() только событие POLLHUP, затем закройте дескриптор файла и завершите программу.
       /* 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);
       }

Guess you like

Origin blog.csdn.net/BillyThe/article/details/132774658