单线程序列读取多用户usocket服务器

问题描述 投票:3回答:3

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

有没有办法一次只读一个块(有效)只用一个线程而不必经常等待输入?或者我真的必须一遍又一遍地打电话给read-byte直到我拥有一切?

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

更新:我特别寻找不需要读取字符的二进制解决方案,因此涉及read-char-no-hanglisten等的解决方案除非具有二进制等效值,否则无济于事。我想不使用字符,因为某些字符编码(如UTF-8)可能有无效的字节序列,没有字符表示,我希望能够处理任何字节序列。我一直在寻找一次性不需要一次读取一个字节的解决方案,或确认不存在这样的解决方案(在标准中),在这种情况下我很想听听最方便的可以提供完成该任务所需的最低限度的库。这不只是一次读取一个字节不是最快的方式,它还需要我编写的任何函数以非阻塞的方式来为每个字节使用usocketwait-for-input函数(因为listen不能用于字节stream),这将需要函数来了解套接字,我将不得不编写一个过于特定的read-all-bytes函数,它不能用于文件流。这是可能的,但我希望有一个更通用的方式。

common-lisp polling usocket
3个回答
2
投票

好吧,我尝试了一个来自rosetta代码的代码,一个示例usocket echo服务器,该技巧就是创建自己的读取函数。在这种情况下,read-all并等待: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.

希望这可以帮助


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套接字实现的更多信息是here


0
投票

更新:实现这实际上并不适用于clisp - 仅在SBCL上测试,其中(listen)似乎至少对网络流做“你可能会想到的”。因此,这不是一个可移植的解决方案..除非您在下面的循环中替换'(listen)'以使用某种形式的#+功能。

因为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)))
© www.soinside.com 2019 - 2024. All rights reserved.