Как я могу буферизировать неблокирующий ввод-вывод?
-
10-07-2019 - |
Вопрос
Когда мне нужен буферизованный ввод-вывод для блокирующего файлового дескриптора, я использую stdio.Но если я переведу файловый дескриптор в неблокирующий режим в соответствии с ручным stdio, буферизация станет непригодной для использования.После некоторых исследований я вижу, что BIO можно использовать для буферизации неблокирующего ввода-вывода.
Но, может быть, есть и другие альтернативы?
Мне это нужно, чтобы избежать использования потоков в среде с несколькими подключениями.
Решение
Я думаю, то, о чем вы говорите, - это Схема реактора.Это довольно стандартный способ обработки большого количества сетевых подключений без потоков, и он очень распространен в серверных движках многопользовательских игр.Другой реализацией (на python) является скрученная матрица.
Основным алгоритмом является:
- имейте буфер для каждого сокета
- проверьте, какие сокеты готовы к чтению (select(), poll() или просто повторите)
- для каждого гнезда:
- вызовите recv() и накапливайте содержимое в буфере сокета до тех пор, пока recv не вернет 0 или не выдаст ошибку с EWOULDBLOCK
- вызовите обработчик данных прикладного уровня для сокета с содержимым буфера
- очистите буфер сокета
Другие советы
Я вижу, что теперь вопрос отредактирован и, по крайней мере, более понятен, чем раньше.
В любом случае, разве это не противоречие?
- Вы делаете ввод-вывод неблокирующим, потому что хотите иметь возможность быстро считывать небольшие объемы данных, обычно жертвуя пропускной способностью из-за задержки.
- Вы делаете его буферизованным, потому что вас не так уж сильно волнует задержка, но вы хотите эффективно использовать подсистему ввода-вывода, обменивая задержку на пропускную способность.
Выполнение их обоих одновременно кажется противоречием, и его трудно себе представить.
Какую семантику вы ищете?Если ты сделаешь это:
int fd;
char buf[1024];
ssize_t got;
fd = setup_non_blocking_io(...);
got = read(fd, buf, sizeof buf);
Какого поведения вы ожидаете, если доступно 3 байта?Блокирующий / буферизованный ввод-вывод может блокироваться до тех пор, пока не удастся прочитать больше, удовлетворяя ваш запрос, неблокирующий ввод-вывод вернет 3 доступных байта немедленно.
Конечно, если у вас есть какой-то протокол сверху, который определяет некоторую структуру сообщения, чтобы вы могли знать, что "этот ввод-вывод неполный, я не могу разобрать его, пока у меня не будет больше данных", вы можете сами буферизировать его на этом уровне и не передавать данные вверх, пока не будет получено полное сообщение.
В зависимости от протокола, вполне возможно, что вам потребуется буферизовать чтение для неблокирующего сетевого узла (клиента или сервера). Р>
Как правило, эти буферы предоставляют несколько индексов (смещений), которые одновременно записывают позицию последнего обработанного байта и последнего считанного байта (который либо совпадает, либо превышает обработанное смещение). И они также (должны) обеспечивать более богатую семантику сжатия буфера, прозрачного управления размером буфера и т. Д.
В Java (по крайней мере) пакеты неблокирующих сетевых io (NIO) также предоставляют набор структур данных (ByteBuffer и т. д.), которые направлены на предоставление общей структуры данных. Р>
Либо существуют такие структуры данных для C, либо вы должны свернуть свои собственные. Если у вас есть данные, просто прочитайте столько данных, сколько доступно, и дайте буферу решить такие проблемы, как переполнение (например, чтение байтов через границы кадра сообщения), и используйте смещение маркера, чтобы пометить байты, которые вы обработали.
Как указывало в Android, вам (очень вероятно) нужно будет создавать согласованные буферы для каждого открытого соединения.
Вы можете создать структуру с буферами для каждого дескриптора открытого файла, а затем накапливать эти буферы до тех пор, пока recv () не вернет 0 или пока у вас не будет достаточно данных для обработки в буфере.
Если я правильно понимаю ваш вопрос, вы не сможете буферизовать, потому что, не блокируя, вы пишете в один и тот же буфер с несколькими подключениями (если он глобальный) или просто пишете небольшие фрагменты данных (если он локальный).
В любом случае ваша программа должна быть в состоянии определить, откуда поступают данные (возможно, по файловому дескриптору), и соответствующим образом буферизировать их.
Потоки - это тоже опция, это не так страшно, как многие заставляют это казаться.
Библиотека evcom Райана Даля , которая делает именно то, что вы хотели.
Я использую его в своей работе, и он отлично работает. Имейте в виду, однако, что у него (пока, но скоро) не будет асинхронного разрешения DNS. Райан предлагает udns Майкла Токарева для этого. Я пытаюсь использовать udns вместо блокировки getaddrinfo ().