Как узнать, заполнен ли буфер сокета?

Как узнать, заполнен ли буфер чтения сокета или буфер буфера записи пуст?

Есть ли способ получить статус буфера сокета без системного вызова?

UPDATE: Как насчет этого: я хотел бы получить обратный вызов или сигнал, когда заполнен буфер сокета чтения или буфер буфера записи пуст. Таким образом, я могу прекратить обработку, чтобы обеспечить большее количество операций ввода-вывода на проводе, так как привязка ввода-вывода всегда является проблемой при отправке данных на провод.

Вызов select() - это то, как вы проверяете, есть ли в нем буфер чтения. Не когда он заполнен (я думаю).

Ответы

Ответ 1

Вы можете попробовать ioctl. FIONREAD сообщает вам, сколько байтов сразу читается. Если это то же самое, что размер буфера (который вы могли бы получить и/или установить с помощью другого вызова icotl), тогда буфер заполнен. Аналогично, если вы можете записать столько байтов, сколько размер выходного буфера, то выходной буфер пуст.

Я не понимаю, насколько широко поддерживаются FIONREAD, FIONWRITE и SIOCGIFBUFS (или эквиваленты). Я не уверен, что когда-либо использовал их, хотя у меня есть подлый взгляд, что по той или иной причине я использовал аналогичную функциональность на Symbian.

Требуется ли для вызова режим ядра для вычисления, это зависит от платформы. Смутное стремление избежать системных вызовов не является допустимой методикой оптимизации.

Основной интерфейс сокетов BSD-стиля не говорит ничего о буферах чтения и записи. Когда имеет значение, пустой буфер отправки? Это, конечно же, не означает, что все данные были получены на другом конце сокета - он может сидеть где-то в каком-то маршрутизаторе. Аналогично, ваш "заполненный" буфер чтения не гарантирует, что запись на другом конце будет заблокирована.

Вообще говоря, вы просто читаете/пишите столько, сколько можете, и пусть слой сокетов обрабатывает сложность. Если вы видите много операций ввода-вывода с небольшими размерами, возможно, есть проблемы с производительностью. Но помните, что сокет потока будет отправлять/получать пакет за раз, содержащий блок данных. Если TCP_NODELAY не установлен, это не так, как если бы байты поступали на NIC, и вы могли бы сделать один прочитанный вызов на каждый байт. Они поступают в пакеты, поэтому, скорее всего, они станут доступны для чтения сразу, возможно, 1 к-ит за раз. Вероятно, вы вряд ли сможете ускорить процесс чтения, пока не прочитаете много. На самом деле вы можете сделать это хуже, потому что к тому времени, когда ваш буфер чтения конечной точки будет заполнен, существует риск того, что входящие данные будут отброшены, потому что их некуда будет хранить, что приведет к задержкам и повторным отправкам.

Ответ 2

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

Прежде чем я начну, перейдем к зависанию системных вызовов - вы не можете взаимодействовать с сетевыми стеками на основе ядра (* nix) без переключения и выхода из пространства ядра. Ваша цель должна заключаться в понимании функций стека, поэтому вы можете получить максимальную отдачу от своей системы.

Как узнать, заполнен ли буфер чтения сокета

На эту часть ответили - вы этого не делаете, потому что это не так, как вы должны думать.

Если отправитель (плохо) фрагментирует его TCP-фреймы (обычно из-за отсутствия буферизации маршализированных данных на выходе и с отключением алгоритма Nagle с TCP_NDELAY), ваша идея сократить количество системных вызовов, которые вы делаете, является хорошей идея. Подход, который вы должны использовать, включает настройку "низкого водяного знака" для чтения. Во-первых, вы устанавливаете то, что считаете разумным размером буфера приема, устанавливая SO_RCVBUF с помощью setsockopt(). Затем верните фактический размер буфера чтения с помощью getsockopt(), поскольку вы, возможно, не получите то, о чем попросите.:) К сожалению, не все реализации позволяют вам снова читать SO_RCVBUF, поэтому ваш пробег может отличаться. Затем определите, сколько данных вы хотите прочитать для чтения, прежде чем читать. Установите SO_RCVLOWAT с этим размером, используя setsockopt(). Теперь дескриптор файла сокета будет выбираться только как читаемый, если есть хотя бы этот объем прочитанных данных.

или буфер буфера записи пуст?

Это интересно, поскольку мне нужно было сделать это недавно, чтобы гарантировать, что мой MODUUS/TCP ADU каждый занят своими собственными кадрами TCP, которые требуются спецификации MODBUS (@steve: контроль фрагментации - это один раз, когда вам нужно знать когда буфер отправки пуст!). Что касается оригинального плаката, я очень сомневаюсь, что он действительно этого хочет, и полагает, что он будет намного лучше обслуживать, зная размер буфера отправки до его начала, и периодически проверять количество данных в буфере отправки во время отправки, используя уже описанные методы. Это обеспечило бы более тонкую информацию о пропорции используемого буфера отправки, который можно было бы использовать для более дросселирования производства.

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

Не случайно, что мои ответы на ваши вопросы вращаются вокруг использования select(). Почти во всех случаях (и я понимаю, что сейчас я направляюсь на религиозную территорию!) Приложениям, которым нужно перемещать много данных (внутри и между хостами), лучше всего структурировать как однопоточные государственные машины, используя маски выбора и цикл обработки, основанный на pselect(). В наши дни некоторые ОС (Linux, чтобы назвать один) даже позволяют управлять обработкой сигнала с помощью выбора дескриптора файла. Какая роскошь - когда я был мальчиком...:)

Петр

Ответ 3

Опросите файловый дескриптор с помощью select и нулевого тайм-аута - если он говорит, что он доступен для записи, буфер отправки не заполнен.

(О... без системного вызова. Нет, нет.)

Добавление:

В ответ на ваш обновленный вопрос вы можете использовать два ioctl в сокете TCP: SIOCINQ возвращает количество непрочитанных данных в буфере приема, а SIOCOUTQ возвращает количество неотправленных данных в очереди отправки, Я не верю, что там есть какое-либо асинхронное уведомление о событиях, которое не даст вам опроса.

Ответ 4

Принимая во внимание, что буфер ядра для сокетов живет в kernelspace, я сомневаюсь, что есть какой-либо способ задать размер без syscall.
С помощью системных вызовов вы можете попробовать recv с PEEK.

ret = recv(fd, buf, len, MSG_PEEK);

Будет выдавать recv, но без опорожнения буфера.

Ответ 5

Это невозможно без syscall. Но что проблема с syscalls?

Ответ 6

Я думаю, что есть фундаментальная причина, почему ваш подход ошибочен/обречен. Система не хочет сообщать вам, когда буфер чтения заполнен/буфер записи пуст, поскольку эти события указывают на разрыв в контракте между вами и системой. Если что-то дойдет до этой точки (особенно в направлении чтения), вам будет слишком поздно обеспечить бесперебойную работу стека протоколов. Может появиться еще несколько данных, когда вы, наконец, решите прочитать буфер. Вы должны прочитать буфер до того, как он будет заполнен, что весь пункт буферизованного ввода-вывода.

Ответ 7

Если вы читаете() s в отдельном потоке, SO_RCVLOWAT может блокировать это чтение, пока в буфере не будет достаточного количества данных. К сожалению, poll() и select() игнорируют эту опцию сокета, по крайней мере, в Linux и всегда проверяют наличие одного байта.

Ответ 8

@blaze,

Linux и SO_RCVLOWAT

С уважением, мой опыт отличается от вашего. Я использую низкоуровневые водяные знаки приема буфера в Linux с FC5, в продуктах, которые распространяют видео через IP (как UDP, так и TCP), поэтому я понимаю, насколько важно максимально использовать возможности вашего сетевого стека. Фактически, Linux была одной из первых реализаций, позволяющих вам прочитать низкий водяной знак (и некоторые до сих пор этого не позволяют).:)

Вы указываете, что poll() и select() не соответствуют SO_RCVLOWAT. Я использую pselect() до тех пор, пока я помню, поэтому, возможно, проблема связана с select() и poll(). В любом случае, вы всегда должны использовать pselect() или ppoll(), если они доступны, в предпочтении более старым вызовам, поскольку они могут атомизически изменять маску сигнала программы при входе/выходе из вызова. Если вы понимаете, что это значит, тогда вы поймете, почему это важно в коммерческом программном обеспечении. Если нет, такая дискуссия оправдывает собственную нить.:)

Петр