2017-02-01 17 views
1

Согласно Linux man pages, select поддерживает три типа событий для пробуждения:select() - практическое использование writefds и exceptfds с неблокирующими сокетами TCP?

  • readfds будет следить, чтобы увидеть, если персонажи становятся доступны для чтения
  • writefds будет следить, чтобы увидеть, если пространство доступно для записи
  • exceptfds будут наблюдать за исключением

в поисках практических примеров использования с TCP сокетов ONl ine и в сетевых книгах, я в основном вижу только readfds, даже если код пытается записать в сокет позже.

Но розетка может быть не готова для записи, потому что мы могли ее получить только в readfs, но не в writefds. Чтобы избежать блокировки при записи, я обычно устанавливаю fd сокета в неблокирующий режим. Тогда, если send терпит неудачу, я могу просто поставить очередь данных в некоторый внутренний буфер и отправить его позже (что означает - в следующий раз, когда select() с readfs просыпается). Но это кажется опасным - что, если следующий readfs пробуждение приходит намного позже, а данные, которые будут написаны, просто сидят в нашем буфере, ожидая, теоретически, навсегда?

документация компании Apple также рекомендует использовать writefds: Using Sockets and Socket Streams, смотрите раздел «Обработка событий с чистым POSIX кодексом», цитируя:

вызова выберите в цикле, проходя две отдельных копии этого файл дескриптора set (созданный вызовом FD_COPY) для чтение и запись наборы дескрипторов.

Так вопросы:

  1. яблочно рекомендует использовать writefds только потому, что «правильный официальный путь» или, может быть, есть и другие подходы, как иметь дело с сокет пишет без writefds? И рекомендация Apple кажется мне подозрительной. Если мы поместим сокет в writefds с самого начала, а затем не будем писать ему какое-то время, не будет select() сразу просыпаться только потому, что сокет доступен для записи (и это потому, что мы еще не записали его)?

  2. О exceptfds-Я еще не видел примеров использования его с сокетами TCP. Я читал, что он используется для внеполосных данных. Означает ли это, что я могу игнорировать exceptfds для сокетов TCP, если я имею дело только с основным интернет-трафиком, таким как HTTP, потоковое аудио/видео, игровые серверы и т. Д.?

+1

Что касается exceptfds, протокол Telnet (также используемый в других протоколах, например, в соединении управления FTP) использует TCP URG (OOB) для некоторых команд. Соединение IIRC Ctrl-C'ing приводит к его использованию. – ninjalj

ответ

2

яблочно рекомендует использовать writefds только потому, что «правильный официальный путь» или, может быть, есть и другие подходы, как иметь дело с гнездо пишет без writefds?

Другой подход (что вы видели в учебниках вы смотрели на), чтобы предположить, что записи буфера всегда будет достаточно большим, чтобы сразу вместить все данные, которые вы хотите отправить к нему, а просто слепо позвонить Отправить(), когда вам нужно.

Это упрощает код, но это не очень хороший подход - возможно, он достаточно хорош для игрушечной/примерной программы, но я бы не хотел делать это предположение в коде качества продукции, потому что это означает что-то плохое произойдет, если/когда ваша программа генерирует достаточно данных за раз, чтобы заполнить выходной буфер сокета. В зависимости от того, как вы (ошибочно) обрабатывали вызов send(), ваша программа переходила бы в цикл спина (вызов send() и получение EWOULDBLOCK снова и снова, пока не было достаточно места для размещения всех данных) , или ошибка (если вы рассматривали EWOULDBLOCK/short-send() как фатальное условие ошибки) или отбросить некоторые байты исходящих данных (если вы просто игнорировали возвращаемое значение send()). Ни один из них не является изящным способом обработки ситуации с полным выходным буфером.

Если положить гнездо в writefds с самого начала, а затем не писать ему на какое-то время, не подберет() просыпаются сразу только потому, что сокет доступен для записи (и это потому, мы еще не писали )?

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

О exceptfds -Я еще не видел примеров использования его с помощью TCP сокетов. Я читал, что он используется для внеполосных данных.

Как правило, exceptfds не используется для многих (не является функцией внеполосных данных TCP, AFAIK). Единственный раз, когда я видел, что он используется, заключается в том, что при асинхронном/неблокирующем TCP-подключении под Windows - Windows использует excfds для пробуждения select(), когда попытка асинхронного/неблокирующего TCP-подключения потерпела неудачу.

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

Поскольку TCP автоматически замедляет отправитель до передачи примерно в скорости приемник принимает его на, это, конечно, возможно, что принимающая программа может просто перестать называть ПРИЕМ(), в конечном счете, снижая скорость передачи данных отправителя к нулю. Или, альтернативно, сеть между отправителем и получателем может начать отбрасывать так много пакетов, что скорость передачи становится фактически нулевой, даже если приемник вызывает recv(), как и предполагалось. В любом случае это означало бы, что ваши данные в очереди могут очень хорошо сидеть в вашем буфере исходящих данных в течение длительного времени - возможно, не навсегда в последнем случае, поскольку в конечном итоге полное соединение TCP-соединения в конечном итоге выйдет из строя; и в первом случае вам нужно отлаживать принимающую сторону больше, чем отправляющая сторона.

Настоящая проблема возникает, когда ваш отправитель генерирует данные быстрее, чем ваш получатель может получить его (или, если это иначе, быстрее, чем сеть может его транспортировать) - в этом случае, если вы в очереди «лишние» данные в FIFO со стороны отправителя, что FIFO может расти без ограничений, пока, в конечном счете, ваш процесс отправки не завершится из-за исчерпания памяти - определенно нежелательного поведения.

Есть несколько способов справиться с этим; одним из способов было бы просто контролировать количество байтов, которые в настоящее время хранятся в FIFO, и когда он достигает определенного порога (например, один мегабайт или что-то, что представляет собой «разумный» порог, будет зависеть от того, что делает ваше приложение), сервер может решить, что клиент просто не может выполнить достаточно хорошо и закрыть отправляющий сокет в целях самозащиты (и, конечно же, освободить связанную очередь FIFO). Это хорошо работает во многих случаях, хотя, если ваш сервер когда-либо генерировал/выдавал больше, чем этот объем данных мгновенно, он мог бы пострадать от ложных срабатываний и в конечном итоге ненадлежащим образом отключить клиентов, которые действительно хорошо работают.

Другим подходом (который я предпочитаю, когда это возможно) является создание сервера так, чтобы он только только генерирует больше выходных данных для сокета, когда в данный момент нет данных вывода для этого сокета. то есть, когда сокет выбирает как готовый для записи, сбрасывает столько же имеющихся данных, сколько вы можете из очереди FIFO в сокет. Когда очередь FIFO пуста и у вас есть данные, которые вы хотите генерировать исходящие байты от и, сокет готов к записи, , что - это единственный раз, когда вы можете создать еще несколько байтов выходных данных и поместить их в очередь FIFO. Повторите это навсегда, и размер вашей очереди FIFO никогда не будет больше, чем объем данных, сгенерированных вами на одной итерации вашего шага генерации более данных-байтов, независимо от того, насколько медленен клиент.

+1

Выбор для удобочитаемости следует использовать только после EAGAIN/EWOULDBLOCK. В противном случае, когда вам есть что написать, просто напишите его сразу и проверьте. Выполнение этого с помощью 'select()' просто пустая трата времени. Ваш последний параграф выглядит несколько круговым: вы генерируете только выход, когда есть место, и вы проверяете только комнату, когда есть выход. – EJP

+0

@EJP Вам нужно сделать различие между (a) «У меня есть некоторая структура данных, которую я хочу сериализовать в байтах для добавления в FIFO» и (b) «У меня есть байты в моем FIFO, которые я хочу отправить() к гнезду ". Вы сериализуете и добавляете к FIFO, когда (a &! B), и вы выбираете для записи и отправки(), когда (b). –

+0

@EJP, чтобы быть ясным, мой проект должен иметь ровно один вызов select() во всей программе и никогда не блокировать, кроме одного вызова. Таким образом, в случае, когда есть сокет с байтами, присутствующими в FIFO исходящих данных, я включаю этот сокет в writefds select(), так что select() будет возвращаться, как только сокет имеет свободное пространство для отправки () к. –

-1

Когда select возвращается, readfds и writefds модифицируются, чтобы показать состояние в конце select:

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

Проверьте каждый fd в каждом наборе и действуйте соответственно: прочитайте, если его можно прочитать, напишите, если он может быть записан.

Возникает вопрос: как нам избежать немедленного возврата с select(), которому было дано writefds? Во многих случаях сокеты готовы к записи большую часть времени. Вы должны управлять своим writefds.

Обновления от комментариев по @EJP:

использовать только writefds для любого отдельного сокета, если вы испытали короткую запись или EAGAIN/EWOULDBLOCK, чтобы сказать вам, когда сокет стал доступен для записи снова, и прекратить его использование в качестве как только передача полностью преуспела.

+2

Используйте только 'writefds' для любого отдельного сокета, если вы столкнулись с короткой записью или EAGAIN/EWOULDBLOCK, чтобы рассказать вам, когда сокет снова стал доступен для записи, и прекратите использовать его, как только передача будет полностью выполнена. – EJP