Как проверить, закрыл ли другой конец мой поток сокетов из одного потока?

Часто задаваемые вопросы по 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 блоки на входе отсутствуют, что заставит поток ждать, пока первые два клиента не отправят сообщение.

Решения, которые я имею в виду, но не нашли после некоторого определенного поиска, (в порядке убывания предпочтений):

  1. Функция, иначе эквивалентная listen это возвращает t если чтение в потоке целей вернет end-of-file маркер. (Замена listen выше с этой условной функцией позволил бы остальной части работать как написано)
  2. Функция, иначе эквивалентная wait-for-input который возвращает список закрытых сокетов, которые вызывают его отключение. (В этом случае я мог бы перебрать список закрытых сокетов, проверить, что они действительно закрыты с предложенным read технику и закрывайте / высовывайте их по мере необходимости)
  3. Функция, иначе эквивалентная wait-for-input который возвращает первый закрытый сокет, который вызвал его отключение. (Как #2, но медленнее, потому что это сокращает самое большее одно неактивное соединение за итерацию)
  4. Отслеживание того, сколько времени прошло с тех пор, как я получил вход от каждого сокетного соединения, и закрытие их независимо от определенного периода бездействия. (Что я, вероятно, хотел бы сделать в любом случае, но именно это потенциально могло бы удерживать кучу мертвых соединений гораздо дольше, чем необходимо)
  5. Функция, которая пытается 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))))

должен делать хорошо.

Другие вопросы по тегам