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

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

В ситуации, когда через некоторое время ни один клиент не говорил, а третий клиент отключился, кажется, что нет хорошего способа выяснить это и закрыть это конкретное соединение сокета. Мне пришлось бы читать их последовательно, за исключением того, что, поскольку read блокируется без ввода, это заставит поток ждать, пока первые два клиента не отправят сообщение.

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

  1. Функция, иначе эквивалентная listen, которая возвращает t, если чтение потока целей вернет маркер end-of-file. (Заменив listen выше этой условной функцией, остальная часть будет работать, как написано)
  2. Функция, эквивалентная wait-for-input, которая возвращает список закрытых сокетов, из-за которых она отключается. (В этом случае я мог бы пройтись по списку закрытых сокетов, проверить, действительно ли они закрыты с помощью предложенного метода read, и закрыть/вытолкнуть их по мере необходимости)
  3. Функция, эквивалентная wait-for-input, которая возвращает первый закрытый сокет, вызвавший его срабатывание. (То же, что и № 2, но медленнее, поскольку удаляет не более одного неактивного соединения за итерацию)
  4. Отслеживание того, сколько времени прошло с тех пор, как я получил входные данные от каждого подключения к сокету, и закрытие их независимо от определенного периода бездействия. (Что я, вероятно, хотел бы сделать в любом случае, но просто это могло бы сохранить множество неработающих соединений гораздо дольше, чем необходимо)
  5. Функция, которая пытается получить read-char из потока с мгновенным тайм-аутом, возвращает t, если встречает :eof, и unread-chars что-либо еще (возвращая nil либо после истечения времени ожидания, либо после отмены чтения). (Это крайняя мера, так как кажется, что было бы тривиально легко взломать неочевидным, но смертельным способом).

Кроме того, если я думаю об этом совершенно неправильно, укажите это тоже.


person Inaimathi    schedule 22.07.2012    source источник
comment
вы пытаетесь эмулировать цикл событий select на этих сокетах?   -  person Vsevolod Dyomkin    schedule 22.07.2012
comment
@VsevolodDyomkin - Да, я пытаюсь построить цикл событий, хотя я не совсем уверен, что такое цикл событий select.   -  person Inaimathi    schedule 22.07.2012


Ответы (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))))

должен сделать красиво.

person Inaimathi    schedule 22.07.2012