Skip to content

Latest commit

 

History

History
170 lines (121 loc) · 8.31 KB

File metadata and controls

170 lines (121 loc) · 8.31 KB

Продвинутые темы

Блокировка

Блокировка это буквально "сон" (в контексте потоков выполнения), когда поток выполнения "засыпает", останавливается на определенном вызове, пока не произойдет какое-то событие.

Множество системных вызовов являются блокирующими. Примеры блокировки:

  • recvfrom() - ожидание получения данных.
  • recv() - ожидание получения данных.
  • accept() - ожидание подключения.
  • ...

Когда вы создаете сокет через функцию socket(), по умолчанию, ядро делает его блокирующим.

Изменить это поведение, как и для других файловых дескрипторов, можно с помощью функции fcntl(), с ее помощью можно конфигурировать поведение файлового дескриптора.

Пример установления сокета неблокирующим:

#include <unistd.h>
#include <fcntl.h>
/*
...
*/
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
/* делаем сокет неблокирующим: */
fcntl(sockfd, F_SETFL, O_NONBLOCK);
/*
...
*/

Делая сокет неблокирующим, в будущем вы можете его "опрашивать" о состоянии. Но данный пример, установки сокета в неблокирующее состояние и потом его опрашивание - плохая идея.

Если вы поместите свою программу в режим ожидания, ожидая данных в сокете, вы будете впустую тратить огромное количество времени CPU.

Более эффективное решение проверки наличия данных, ожидающих чтения - функция poll().

Функция poll()

poll() позволяет делать синхронное I/O мультиплексирование.

Мультиплексирование, в конетксте сокетов - это возможность обрабатывать ввод/вывод двух и более сокетов.

То есть, у вас есть набор сокетов, и вы обрабатывайте ввод/вывод тех, данные которых готовы.

Предупреждение: poll() ужасно медленный, когда дело доходит до огромного числа подключений. Хорошая альтернатива - библиотека libevent.

Пока считывать с сокетов нечего, вызов poll() уводит поток выполнения в сон, сберегая ресурсы CPU.

Сигнатура:

#include <poll.h>

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

Аргументы:

  • struct pollfd fds[] - массив дескрипторов сокетов, которые мы хотим отслеживать.

  • nfds_t nfds - количество элементов в массиве.

  • int timeout - таймаут в миллисекундах.

Функция возвращает количество элементов в массиве, в которых произошли события.

Структура ```struct pollfd`` имеет такой вид:

struct pollfd {
    int fd; // дескриптор сокета
    short events; // битовая маска событий, которые нам нужны
    short revents; // битовая маска событий, которые произошли при возврате из функции
}

Поле events - это побитовое ИЛИ (bitwise-OR) следующих макросов:

  • POLLIN - сигнализирует, когда сокет готов к чтению данных, через функцию recv().
  • POLLOUT - сигнализирует, когда сокет готов к отправке данных, через функцию send().

После возврата из функции poll(), вы можете проверить поле reventsна наличие POLLIN или POLLOUT, указывающих что произошло событие.

Пример использования poll() со стандартным вводом (дескриптор файла - 0) и таймаутом:

poll_stdin.c

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

$ ./poll_stdin
Press 'RETURN' or wait 2.5 seconds for timeout
# нажимаем ENTER...
File descriptor 0 is ready to read.

А если просто подождать 2.5 секунды, то сработает таймаут и будет такой результат:

$ ./poll_stdin
Press 'RETURN' or wait 2.5 seconds for timeout
# просто ждем...
Poll timed out!

Пример чат-сервера, с которым можно общаться с помощью telnet:
poll_chatserver.c

  1. Запускаем poll_chatserver
$ ./poll_chatserver
pollserver: waiting for connections on port 9034 ...
pollserver: new connection from 127.0.0.1 on socket 4
pollserver: recv from fd 4: hey i am Borya!
pollserver: recv from fd 4: how are you guys?
pollserver: new connection from 127.0.0.1 on socket 5
pollserver: recv from fd 5: Hey Borya! I'm fine!
pollserver: recv from fd 5: pollserver: recv from fd 4: pollserver: socket 5 closed by client.
pollserver: recv from fd 4: pollserver: socket 4 closed by client.
  1. В другом терминале подключаемся к нему через telnet:
$ telnet localhost 9034
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

# вводим наши сообщения
  1. В другом терминале также подключаемся с telnet

  2. Происходит обмен сообщениями!

pollserver: new connection from 127.0.0.1 on socket 4
pollserver: recv from fd 4: hey i am Borya!
pollserver: recv from fd 4: how are you guys?
pollserver: new connection from 127.0.0.1 on socket 5
pollserver: recv from fd 5: Hey Borya! Im fine!
pollserver: recv from fd 5: pollserver: recv from fd 4: pollserver: socket 5 closed by client.
pollserver: recv from fd 4: pollserver: socket 4 closed by client.
  1. Клиенты также видят у себя сообщения в telnet, так как сервер их ретранслирует остальным:
hey i am Borya! # наши сообщения
how are you guys?
Hey Borya! Im fine! # ответ от другого клиента

Чтобы выйти из telnet нажмите ctrl + ], затем введите команду quit

select()

Функция select() очень похожа на poll(), делает ту же работу - предоставляет неблокирующее мультиплексирование нескольких сокетов, но является устаревшой.

Пример программы, которая считывает ввод с дескриптора стандартного ввода stdin или срабатывает таймаут:

select_stdin.c

Это полная аналогия с использование функции poll(): poll_stdin.c

Как понять, что клиент закрыл соединение?

  • select() вернет этот сокет как "готовый к чтению".
  • recv() вернет 0 байтов.