Как проверить, закрыл ли другой конец мой поток сокетов из одного потока?
Часто задаваемые вопросы по usocket предполагают, что я должен это сделать, прочитав socket-stream
и проверка на end-of-file
результат. Это работает в случае, когда у меня есть один активный поток на сокет, но это, кажется, не удовлетворяет в случае, когда я пытаюсь обслуживать несколько сокетов в одном потоке.
Рассмотреть что-то вроде
(defparameter *socket* (socket-listen "127.0.0.1" 123456))
(defparameter *client-connections*
(list (socket-accept *socket*)
(socket-accept *socket*)
(socket-accept *socket*)
(socket-accept *socket*)))
Для этого упражнения предположим, что на самом деле у меня есть четыре клиента, подключающихся к нему. Кажется, что способ обслужить их из одного потока - это что-то вроде
(wait-for-input *client-connections*)
(loop for sock in *client-connections*
for stream = (socket-stream sock)
when (listen stream)
do (let ((line (read-line stream nil :eof)))
(if (eq line :eof)
(progn (delete sock *client-connections*)
(socket-close sock))
(handle sock line))))
За исключением того, что это не будет работать, потому что отключенный сокет все еще возвращает nil
в listen
и попытка read
из активного сокета без сообщений будет блокировать, но wait-for-intput
немедленно возвращается, когда в миксе есть закрытый сокет, даже когда ни один другой сокет не имеет готового сообщения (хотя кажется, что он не в состоянии указать, какие сокеты вызвали его возврат).
В ситуации, когда ни один клиент не говорил в течение короткого времени, а третий клиент отключается, кажется, что нет хорошего способа выяснить это и закрыть конкретное сокетное соединение. Я должен был бы прочитать их по порядку, за исключением того, что с read
блоки на входе отсутствуют, что заставит поток ждать, пока первые два клиента не отправят сообщение.
Решения, которые я имею в виду, но не нашли после некоторого определенного поиска, (в порядке убывания предпочтений):
- Функция, иначе эквивалентная
listen
это возвращаетt
если чтение в потоке целей вернетend-of-file
маркер. (Заменаlisten
выше с этой условной функцией позволил бы остальной части работать как написано) - Функция, иначе эквивалентная
wait-for-input
который возвращает список закрытых сокетов, которые вызывают его отключение. (В этом случае я мог бы перебрать список закрытых сокетов, проверить, что они действительно закрыты с предложеннымread
технику и закрывайте / высовывайте их по мере необходимости) - Функция, иначе эквивалентная
wait-for-input
который возвращает первый закрытый сокет, который вызвал его отключение. (Как #2, но медленнее, потому что это сокращает самое большее одно неактивное соединение за итерацию) - Отслеживание того, сколько времени прошло с тех пор, как я получил вход от каждого сокетного соединения, и закрытие их независимо от определенного периода бездействия. (Что я, вероятно, хотел бы сделать в любом случае, но именно это потенциально могло бы удерживать кучу мертвых соединений гораздо дольше, чем необходимо)
- Функция, которая пытается
read-char
из потока с мгновенным таймаутом возвращаетt
если он сталкивается с:eof
, а такжеunread-char
что-нибудь еще (возвращениеnil
после тайм-аута или прочтения). (Который является последним средством, так как кажется, что было бы легко сломаться неочевидным, но смертельным способом).
Кроме того, если я думаю об этом совершенно неправильно, укажите на это тоже.
1 ответ
Оказывается, то, что я упоминаю как вариант 2 выше, существует.
wait-for-input
по умолчанию возвращает полный список отслеживаемых соединений для управления памятью (по сообщениям, кто-то был очень обеспокоен cons
новые списки для результата), но он имеет &key
параметр, который говорит ему просто вернуть соединения, которым есть что сказать.
(wait-for-input (list conn1 conn2 conn3 conn4) :ready-only t)
это то, что я искал там. Это возвращает все готовые соединения, а не только те, которые собираются сигнализировать end-of-file
Таким образом, цикл все еще должен обрабатывать оба случая. Что-то вроде
(loop for sock in (wait-for-input *client-connections* :ready-only t)
for stream = (socket-stream sock)
do (let ((line (read-line stream nil :eof)))
(if (eq line :eof)
(progn (delete sock *client-connections*)
(socket-close sock))
(handle sock line))))
должен делать хорошо.