Неблокирующий ввод-вывод UNIX: O_NONBLOCK против FIONBIO

92

В каждом примере и обсуждении, с которыми я сталкиваюсь в контексте программирования сокетов BSD, кажется, что рекомендуемый способ установить дескриптор файла в неблокирующий режим ввода-вывода - использовать этот O_NONBLOCKфлаг fcntl(), например,

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

Я занимаюсь сетевым программированием в UNIX более десяти лет и всегда использовал для этого FIONBIO ioctl()вызов:

int opt = 1;
ioctl(fd, FIONBIO, &opt);

Никогда особо не задумывался, почему. Просто так научился.

Есть ли у кого-нибудь комментарии относительно возможных достоинств того или другого? Я предполагаю, что локус переносимости несколько отличается, но не знаю, в какой степени, поскольку ioctl_list(2)не относится к этому аспекту отдельных ioctlметодов.

Алексей Балашов
источник

Ответы:

135

До стандартизации существовали ioctl(... FIONBIO... )и fcntl(... O_NDELAY... ), но они вели себя непоследовательно в разных системах и даже в пределах одной системы. Например, было обычным FIONBIOделом работать с сокетами и O_NDELAYработать с ttys, с большим количеством несоответствий для таких вещей, как каналы, FIFO и устройства. И если вы не знаете, какой у вас файловый дескриптор, вам придется установить оба, чтобы быть уверенным. Но кроме того, неблокирующее чтение без доступных данных также указывалось непоследовательно; в зависимости от ОС и типа файлового дескриптора при чтении может возвращаться 0 или -1 с ошибкой EAGAIN или -1 с ошибкой EWOULDBLOCK. Даже сегодня установка FIONBIOилиO_NDELAYв Solaris при чтении без данных возвращается 0 на tty или конвейере или -1 с ошибкой EAGAIN на сокете. Однако значение 0 неоднозначно, поскольку оно также возвращается для EOF.

POSIX решил эту проблему O_NONBLOCK, представив стандартное поведение в различных системах и типах файловых дескрипторов. Поскольку существующие системы обычно хотят избежать каких-либо изменений поведения, которые могут нарушить обратную совместимость, POSIX определил новый флаг, а не предписывал конкретное поведение для одного из других. Некоторые системы, такие как Linux, обрабатывают все 3 одинаково, а также определяют EAGAIN и EWOULDBLOCK с одним и тем же значением, но системы, желающие сохранить какое-либо другое устаревшее поведение для обратной совместимости, могут сделать это при использовании старых механизмов.

Новые программы должны использовать fcntl(... O_NONBLOCK... )в соответствии со стандартом POSIX.

mark4o
источник
6
Я предпочитаю использовать для этого ioctl (), потому что для включения неблокирующего режима мне нужно всего один системный вызов, а не два для fcntl (). Кроме того, Windows ioctlsocket () API эквивалентен ioctl () для целей этой функциональности.
Вез Ферлонг,
nginx делает это, если может, и отмечает это комментарием «ioctl (FIONBIO) устанавливает неблокирующий режим с одним системным вызовом». Теперь есть accept2, который позволяет вам принять соединение и перевести его в неблокирующий режим в том же системном вызове.
Eloff
6

Как сказал @Sean, fcntl()он в значительной степени стандартизирован и поэтому доступен на всех платформах. ioctl()Функция предшествует fcntl()в Unix, но не стандартизирована. К ioctl()счастью, это сработало для вас на всех актуальных для вас платформах, но это не гарантируется. В частности, имена, используемые для второго аргумента, являются загадочными и ненадежными для разных платформ. Действительно, они часто уникальны для конкретного драйвера устройства, на который ссылается файловый дескриптор. ( ioctl()Вызовы, использованные для устройства с растровой графикой, работающего на ICL Perq под управлением PNX (Perq Unix) двадцать лет назад, никогда не переводились, например, ни на что другое.)

Джонатан Леффлер
источник
5

Я считаю, fcntl()что это функция POSIX. Где ioctl()это стандартная вещь UNIX. Вот список POSIX io . ioctl()это очень специфичная вещь для ядра / драйвера / ОС, но я уверен, что то, что вы используете, работает с большинством разновидностей Unix. некоторые другие ioctl()вещи могут работать только с определенной ОС или даже с определенными версиями ее ядра.

EdH
источник
Я без проблем использовал FIONBIO в AIX, Solaris, Linux, * BSD и IRIX. Но да, я понимаю, что это не будет работать, например, в Windows - это низкоуровневый интерфейс для очень конкретной реализации ядра. Тем не менее, мне интересно, есть ли другие отличительные факторы.
Alex Balashov