Epoll TCP инициирует необходимость последнего вызова read(2)

Учитывая неблокирующий сокет TCP, если вызов

read(sock, buf, bufLen)

возвращает значение ‹ bufLen, безопасно ли затем ждать запускаемого фронтом события EPOLLIN? Или я должен снова позвонить read, чтобы убедиться, что это ноль или EAGAIN?

В моем тестировании все продолжает работать, когда я удаляю последний вызов, я просто хочу знать, гарантировано ли это где-либо или в исходном коде Linux, и могу ли я избавиться от лишнего вызова.


person Max    schedule 14.01.2014    source источник


Ответы (2)


Ответ на ваш вопрос находится в man 7 epoll. Как видите, это зависит от типа сокета (пакет/поток):

Q9 Нужно ли мне постоянно читать/записывать дескриптор файла до тех пор, пока не будет EAGAIN при использовании флага EPOLLET (поведение, запускаемое фронтом)?

A9 Получение события от epoll_wait(2) должно означать, что такой файловый дескриптор готов для запрошенной операции ввода-вывода. Вы должны считать его готовым до тех пор, пока следующее (неблокирующее) чтение/запись не даст EAGAIN. Когда и как вы будете использовать файловый дескриптор, зависит только от вас.

Для файлов, ориентированных на пакеты/маркеры (например, дейтаграммный сокет, терминал в каноническом режиме), единственный способ обнаружить конец пространства ввода-вывода для чтения/записи — продолжать чтение/запись до EAGAIN.

Для файлов, ориентированных на поток (например, канал, FIFO, потоковый сокет), состояние, когда пространство ввода-вывода для чтения/записи исчерпано, также может быть обнаружено путем проверки количества данных, считанных из/записанных в целевой файловый дескриптор. Например, если вы вызываете read(2), запрашивая чтение определенного объема данных, а read(2) возвращает меньшее количество байтов, вы можете быть уверены, что исчерпали пространство ввода-вывода чтения для файла. дескриптор. То же самое верно и при записи с использованием write(2). (Избегайте этого последнего метода, если вы не можете гарантировать, что отслеживаемый файловый дескриптор всегда ссылается на файл, ориентированный на поток.)

person George Y.    schedule 16.01.2014

Это "безопасно" поскольку не произойдет сбой, но если вы не продолжите звонить read, пока не получите EAGAIN (или ноль, что означает, что другой конец закрыл соединение), вы иногда будете ошибаться. предположения о доступности данных. Хуже всего то, что большую часть времени он будет выглядеть так, как будто он работает нормально.

Оповещение, инициируемое границей, а не уведомление, инициируемое уровнем, гарантирует, что вы получите только одно уведомление, если состояние готовности изменилось с момента последнего вызова epoll_wait, даже если остались данные, которые вы могли прочитать.
Уведомление о событии, инициируемое Edge, действительно иногда ведет себя странно или неинтуитивно в Linux, поэтому оно может делать что-то отличное от того, что вы ожидаете, и, например, дать вам еще одно уведомление, когда поступит больше данных (так что ваш код будет работать в любом случае), но это не то, что гарантируется.
У меня были подобные «сюрпризы» при использовании epoll с eventfd. Ожидается, что в режиме с запуском по фронту все потоки, которые уже заблокированы, пробуждаются (все в одно и то же время и ровно один раз), и все, вызывающие epoll_wait после события, блокируются до тех пор, пока событие не будет использовано и подается сигнал снова. На самом деле он пробуждает первый поток, вызвавший epoll_wait. И снова сюрприз, режим с запуском по уровню работает именно так, как вы хотели бы, за исключением того, что вы должны использовать событие, чтобы иметь возможность подготовить его снова, для чего нет правильного способа сделать это (поскольку вы должны сделать это ровно один раз или вы заблокируете read).

Таким образом, если вы не израсходуете все данные и позже дождетесь повторного уведомления, вам может повезти, и оно «все равно сработает», или вы можете ждать довольно долго, возможно, навсегда. Поэтому я рекомендую определенно продолжать чтение, пока не получите EAGAIN, это единственная по-настоящему надежная вещь, позволяющая избежать неожиданностей.

Обратите внимание, что вы можете морить голодом медленных отправителей, если будете продолжать наивно читать. Если у вас очень быстрый отправитель, и вы продолжаете читать на быстром отправителе, то вы никогда не увидите EAGAIN (по крайней мере, до тех пор, пока другой конец продолжает отправлять!), и вы полностью лишите других отправителей.
Это поэтому имеет смысл поместить все готовые дескрипторы в список и читать их циклически, удаляя их из списка, когда они возвращают EAGAIN.

person Damon    schedule 14.01.2014
comment
Это хороший, практичный набор советов по использованию epoll с запуском по фронту. Программам всегда нужно читать до тех пор, пока чтение не завершится с ошибкой errno=EAGAIN (которое также называется EWOULDBLOCK). Делая это, он может легко отключить другие удаленные соединения, если кто-то записывает большой файл или большой поток данных. Edge-trigger epoll заметно быстрее, но имеет свои проблемы.... - person dturvene; 11.02.2018
comment
...изменение с момента последнего звонка вам... отслеживает ли epoll события, отправляемые слушателям? - person d9ngle; 07.04.2020
comment
@d9ngle: Не то, чтобы я знал об этом, и я бы серьезно в этом сомневался. Работа с запуском по краю больше похожа на грязный флаг. Сокет готов, флаг установлен. Вы звоните epoll_wait, флаг снят. Все остальное твои проблемы. В режиме триггера уровня, как только дескриптор становится готовым, ядро ​​отслеживает, сколько байтов было получено (или осталось в буфере отправки) и сколько байтов было прочитано (или записано), и epoll_wait блокирует или не блокирует. в отношении того. Вот почему запуск по фронту также требует меньше накладных расходов. Ядро делает меньше вещей (... вместо этого вы делаете). - person Damon; 08.04.2020