Однопоточный многопользовательский сервер usocket с последовательным чтением

Я пытаюсь написать простую серверную программу с использованием библиотеки usocket, которая будет выполнять относительно тривиальную задачу - скажем, возвращать данные обратно. Я хочу сделать это в состоянии сделать это с несколькими клиентами, не блокируя один поток во время ожидания ввода от любого отдельного клиента. Я обнаружил, что можно проверить готовность ввода к данному сокету, используя wait-for-input с :timeout 0, Но мне трудно получить read-sequence работать так, как я хочу. Если я дам ему массив из 50 элементов, а доступно только 5, он будет ждать до 50 доступных, чтобы поместить их в массив.

Есть ли способ читать блоки за раз (эффективно) только с одним потоком без необходимости ждать ввода? Или мне действительно нужно просто позвонить read-byte снова и снова, пока я не получу все?

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

ОБНОВЛЕНИЕ: я специально ищу бинарные решения, которые не требуют чтения символов, поэтому решения с участием read-char-no-hang, listenи т. д. не сильно поможет, если у них нет двоичного эквивалента. Я хочу не работать с символами, потому что некоторые кодировки символов, такие как UTF-8, могут иметь недопустимые последовательности байтов, для которых нет представления символов, и я хочу иметь возможность обрабатывать любую последовательность байтов. И я специально ищу либо решения, которые не требуют чтения одного байта за раз и снова, либо подтверждение того, что такого решения не существует (в стандарте), и в этом случае я хотел бы услышать о наиболее удобном библиотека, которая может обеспечить минимум, необходимый для этого. Дело не только в том, что чтение одного байта за раз - это не самый быстрый способ, для этого требуется любая функция, которую я пишу, чтобы сделать это неблокирующим способом, чтобы использовать usocket"s wait-for-input функция для каждого байта (так как listen не работает с байтовыми потоками), что потребовало бы, чтобы функция знала о сокете, и мне пришлось бы написать чрезмерно специфичный read-all-bytes функция, которая не будет работать, скажем, с файловыми потоками. Это возможно, но я надеюсь, что есть более общий способ.

2 ответа

Ну, я пробую код из кода rosetta, пример сервера эхо-сообщений usocket, функция, которая заключается в том, чтобы создать собственную функцию для чтения. В этом случае прочитайте все и ждите: eof, я проверил его с помощью telnet, и он работает:

код:

;; Sample usocket echo server from Rosetta Code
;; http://rosettacode.org/wiki/Echo_server#Common_Lisp

(ql:quickload (list :usocket))

(defpackage :echo (:use :cl :usocket))

(in-package :echo)

(defun read-all (stream)
  (loop for char = (read-char-no-hang stream nil :eof)
     until (or (null char) (eq char :eof)) collect char into msg
     finally (return (values msg char))))

(defun echo-server (port &optional (log-stream *standard-output*))
  (let ((connections (list (socket-listen "127.0.0.1" port :reuse-address t))))
    (unwind-protect
     (loop (loop for ready in (wait-for-input connections :ready-only t)
          do (if (typep ready 'stream-server-usocket)
             (push (socket-accept ready) connections)
             (let* ((stream (socket-stream ready))
                (msg (concatenate 'string "You said: " (read-all stream))))
               (format log-stream "Got message...~%")
               (write-string msg stream)
               (socket-close ready)
               (setf connections (remove ready connections))))))
      (loop for c in connections do (loop while (socket-close c))))))

Инициализировать в lisp:

CL-USER> (in-package :echo)
#<PACKAGE "ECHO">
ECHO> (echo-server 12321)
Got message...

тест с помощью telnet:

╭─toni@Antonios-MBP  ~ ‹ruby-2.2.3@laguna› ‹1.7› ‹SBCL 1.3.0›
╰─$ telnet 127.0.0.1 12321
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello, TCP                       #<= Press enter
You said: Hello, TCP
Connection closed by foreign host.

Надеюсь это поможет

Я тоже боролся с этим.

Одна НЕ переносимая опция, которую я в конечном итоге использовал в проекте, - это реализация по умолчанию SBCL, которую я использую.

С буфером:

(defparameter buf-in (make-array 1024 :element-type '(unsigned-byte 8)))
...
;; suppose variable new-client is your usocket object
(setf my-out (multiple-value-list (sb-bsd-sockets:socket-receive
                     (usocket:socket new-client) bufin nil)))

Вывод будет содержать:

(Your buffer, length, address of peer who sent it)

Больше информации для реализации сокета SBCL здесь

Обновление: поняли, что на самом деле это не работает на clisp - проверено только на SBCL, где (listen), похоже, делает "то, что вы думаете", по крайней мере, для сетевых потоков. Следовательно, это не переносимое решение... если вы не замените '(слушать)' в приведенном ниже цикле, чтобы использовать какую-либо форму функций #+.

поскольку прослушивание не работает с байтовыми потоками

(listen) отлично работает с байтовыми потоками на sbcl. Это должно развязать здесь гордионный узел и позволить легко написать что-нибудь вроде:

(defun read-sequence-no-hang (seq stream start end)
  (loop
     for i from start below end
     for num-bytes-read = 0 then (1+ num-bytes-read)
     while (listen stream)
     do (setf (elt seq i) (read-byte stream))
     finally (return num-bytes-read)))
Другие вопросы по тегам