Однопоточный многопользовательский сервер 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)))