Блокировка это буквально "сон" (в контексте потоков выполнения), когда поток выполнения "засыпает", останавливается на определенном вызове, пока не произойдет какое-то событие.
Множество системных вызовов являются блокирующими. Примеры блокировки:
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() позволяет делать синхронное 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) и таймаутом:
При запуске, мы можем сделать ввод (нажав 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
- Запускаем
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.- В другом терминале подключаемся к нему через
telnet:
$ telnet localhost 9034
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
# вводим наши сообщения-
В другом терминале также подключаемся с
telnet -
Происходит обмен сообщениями!
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.- Клиенты также видят у себя сообщения в telnet, так как сервер их ретранслирует остальным:
hey i am Borya! # наши сообщения
how are you guys?
Hey Borya! Im fine! # ответ от другого клиентаЧтобы выйти из
telnetнажмитеctrl + ], затем введите командуquit
Функция select() очень похожа на poll(), делает ту же работу - предоставляет неблокирующее мультиплексирование нескольких сокетов, но является устаревшой.
Пример программы, которая считывает ввод с дескриптора стандартного ввода stdin или срабатывает таймаут:
Это полная аналогия с использование функции poll():
poll_stdin.c
Как понять, что клиент закрыл соединение?
select()вернет этот сокет как "готовый к чтению".recv()вернет 0 байтов.