3

我正在尝试使用 usocket 库编写一个简单的服务器程序,该程序将执行一项相对微不足道的任务 - 例如,回显数据。我想让它能够对多个客户端执行此操作,而不是在等待来自任何单个客户端的输入时阻止单个线程。我发现使用wait-for-inputwith可以检查给定的套接字是否已准备好输入:timeout 0。但是我很难read-sequence按照自己的意愿工作。如果我给它一个包含 50 个元素的数组,但只有 5 个可用,它会等到 50 个可用时才将它们放入数组中。

有没有办法一次(有效地)读取块,只用一个线程,而不必一直等待输入?还是我真的必须read-byte一遍又一遍地打电话,直到我得到一切?

read-sequence如果有一些等价物只读取当时可用的数量,或者如果有一些函数可以告诉我有多少元素可以读取,那么我可以适当地调整数组的大小,则可以规避这个问题。但我不知道其中任何一个。

更新:我专门寻找不需要读取字符的二进制解决方案,因此涉及read-char-no-hang,listen等的解决方案不会有太大帮助,除非它们具有二进制等效项。我不想使用字符,因为某些字符编码(例如 UTF-8)可能有无效的字节序列,没有字符表示,我希望能够处理任何字节序列。而且我特别在寻找不需要一次又一次地读取一个字节的解决方案,或者确认不存在这样的解决方案(在标准中),在这种情况下,我很想听听最方便的可以提供实现该目标所需的最低限度的库。不仅一次读取一个字节不是最快的方法,它还需要我编写的任何函数以非阻塞方式执行此操作,以便对每个字节使用usocket'wait-for-input函数(因为listen不适用于字节流),这将需要该函数了解套接字,并且我将不得不编写一个read-all-bytes无法与文件流一起使用的过于具体的函数。这是可能的,但我希望有一个更通用的方法。

4

3 回答 3

2

好吧,我尝试了一个来自rosetta code的代码,一个示例usocket echo服务器,诀窍是创建自己的读取函数。在这种情况下,阅读所有内容并等待: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.

希望这可以帮助

于 2015-12-09T07:31:35.083 回答
1

我也为此苦苦挣扎。

我最终在项目中使用的一个不可移植选项默认为我正在使用的 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 套接字实现的更多信息在这里

于 2018-03-28T21:29:02.660 回答
0

Update: Realised this doesn't actually work on clisp - only tested on SBCL, where (listen) seems to do "what you might think" at least for network streams. Therefore this isn't a portable solution.. unless you replace the '(listen)' in the below loop to use some form of #+ features.

since listen doesn't work with byte streams

(listen) works perfectly fine with byte streams on sbcl. That should untie the Gordion knot here, and enable one to easily write something along the lines of:

(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)))
于 2019-03-17T03:19:02.987 回答