Если на удалённом сервере нужно делать какие-то действия, но лень возиться с написанием сетевого сервиса, на помощь приходит xinetd.

Лёгкость написания серверов для xinetd привлекательна, это действительно просто: пишем на любом языке простой скрипт, который работает с stdin и stdout (в простейшем случае это обычный REPL) и получаем одновременно консольную утилиту и сетевой сервер в одном флаконе.
После одной минуты на правку конфига xinetd получаем работающий сервер, к которому можно подключаться telnet-ом или netcat-ом и видеть результат на консоли.



Всё легко и прекрасно. Но раз уж есть (удалённая) консольная утилита, хочется пойти дальше — автоматизировать работу с ней. Не вбивать же сотни запросов руками и не искать же в выдаче на экране, как оно отработало. То есть, говоря сетевым языком, нужна клиентская часть.

Тут естественным образом хочется использовать netcat (nc): пишем простой скрипт, который выдёт команды на stdout и интерпретирует ответы, поступающие ему с stdin и подключаем его к netcat, таким образом превращая в сетевого клиента (хотя сам скрипт об этом и не узнает).

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

В большинстве случаев этого достаточно чтобы работать долго и счастливо.

Но бывают и нюансы.

Написав на радостях несколько таких удалённых утилит, я обнаружил, что иногда процесс общения написанных таким образом клиента и сервера зависает.

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

Кто виноват?


Как работает xinetd: открывает сокет, прицепляет его на стандартные потоки ввода/вывода и запускает внешнюю программулинку.

Как работает netcat (-e, например): открывает сокет, прицепляет его на стандартные потоки ввода/вывода и запускает внешнюю программулинку.

То есть, одинаково.

Соответственно, если обе программулинки заточены на работу с терминалом, никто не проверяет возможность записи или чтения. Если надо читать, просто читают; надо писать — просто пишут.
Только вместо терминала там уже сетевой сокет, а с сетью так никто не работает.

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

Попытки дёшево решить проблему путём вычитывания максимального количества данных проблему не лечат, а только откладывают.

Но больше всего в этой истории меня огорчил netcat.
Не найдя в strace nc никаких вызовов для установки неблокирующего режима сокетов, я заглянул в apt-get source и… действительно не обнаружил ничего подобного.
Netcat работает с сокетами в синхронном режиме, провоцируя возможность зависания, даже без наличия всякой «внешней программы, написанной для консоли».
Печали добавляет то, что netcat по факту является стандартом, хотя, фактически, непригоден для использования в общем случае.

Что делать?


Выхода два:

1. Писать сервер для xinetd с учётом того, что это — сеть (fcntl O_NONBLOCK, select на stdin и stdout и всё такое), но тогда теряется смысл xinetd как простой обёртки для простых вещей. Можно уже писать полноценный сервер.

2. Писать синхронного клиента, который будет дожидаться ответа сервера и не допускать переполнения буферов на чтение и запись — тут теряется простота netcat и надо что-то делать с производительностью чтобы можно было посылать по нескольку запросов одновременно, не переполняя буфер и уметь обрабатывать одновременно несколько ответов.

В моём случае я выбрал полу-второй синхронный вариант:
• послать 10 запросов
• пока есть что слать { послать один запрос; прочитать один ответ }
• прочитать 10 ответов
… что в моём конкретном случае сработало, но как общее решение, конечно, не годится.