яблочно рекомендует использовать 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 никогда не будет больше, чем объем данных, сгенерированных вами на одной итерации вашего шага генерации более данных-байтов, независимо от того, насколько медленен клиент.
Что касается exceptfds, протокол Telnet (также используемый в других протоколах, например, в соединении управления FTP) использует TCP URG (OOB) для некоторых команд. Соединение IIRC Ctrl-C'ing приводит к его использованию. – ninjalj