Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Single-threaded sequence-reading multi-user usocket server

I'm trying to write a simple server program using the usocket library that will perform a relatively trivial task - say, echoing data back. I want to make it able to do this with multiple clients, not having the single thread blocked while waiting for input from any single client. I've found that it's possible to check if a given socket has input ready using wait-for-input with :timeout 0. But I'm having a hard time getting read-sequence to work how I want it to. If I give it an array with 50 elements, and only 5 are available, it will wait until 50 are available to put them in the array.

Is there any way to read blocks at a time (efficiently) with only a single thread without constantly having to wait for input? Or do I really have to just call read-byte over and over until I've got everything?

The problem could be circumvented if there was some equivalent to read-sequence that only read as much as was available at the time, or if there was some function that would tell me how many elements are ready to be read so I could size the array appropriately. But I don't know of either of those.

UPDATE: I'm specifically looking for binary solutions that don't require reading characters, so solutions involving read-char-no-hang, listen, etc won't help much unless they have a binary equivalent. I want to not work with characters because some character encodings, such as UTF-8, can have invalid sequences of bytes for which there is no character representation, and I want to be able to process any sequence of bytes. And I'm specifically looking for either solutions that don't require reading one byte at a time over and over, or confirmation that no such solution exists (in the standard), in which case I'd love to hear about the most convenient library that can provide the minimum necessary to accomplish that. It's not just that reading one byte at a time isn't the fastest way, it also requires whatever function I write to do that in a non-blocking manner to use usocket's wait-for-input function for every byte (since listen doesn't work with byte streams), which would require the function to know about the socket and I would have to write an overly-specific read-all-bytes function that wouldn't work with, say, File streams. It's possible, but I'm hoping there's a more general way.

like image 553
Reepca Avatar asked May 27 '26 11:05

Reepca


1 Answers

Well I try a code from rosetta code, a sample usocket echo server, the function that the trick is to create your own function for read. In this case read-all and wait for :eof, I tested it with telnet and it works:

code:

;; 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))))))

Initialize in lisp:

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

test with 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.

Hope this helps

like image 50
anquegi Avatar answered May 30 '26 06:05

anquegi