orthecreedence / cl-async

Asynchronous IO library for Common Lisp.
MIT License
273 stars 40 forks source link

flexi streams #53

Closed nixz closed 11 years ago

nixz commented 11 years ago

Hi,

I wanted to implement a repl server over tcp.

(defun repl-server () (format t "Starting server.~%") (as:tcp-server nil 9003 ; nil is "0.0.0.0" (lambda (socket stream) (print (eval (read stream)) stream)) (lambda (err) (format t "listener event: ~a~%" err))) ;; catch sigint (as:signal-handler 2 (lambda (sig) (declare (ignore sig)) (as:exit-event-loop))))

This required me to define method (stream-read-char) (stream-unread-char) (stream-write-char) in tcp-streams. However I am now facing issues with the streams being binary and not understanding #\Newline etc. I want the streams to be flexible to accept character streams as well. How do I go about doing this?

-Nix

orthecreedence commented 11 years ago

Nix, be sure when instantiating tcp-server to specify :stream t if you want a stream to come through. As far as wrapping a character stream around a binary stream, I believe you can use flexi-streams for this purpose. Here's a quick example:

(ql:quickload :cl-async)
(ql:quickload :flexi-streams)

(defpackage :char-stream
  (:use :cl))
(in-package :char-stream)

(defun start-server ()
  (let ((char-stream nil))
    (as:tcp-server nil 8090
      (lambda (sock stream)
        (declare (ignore sock))
        ;; wrap the binary stream in a flexi-stream (if this hasn't happened already)
        (unless char-stream
          (setf char-stream (flexi-streams:make-flexi-stream stream :external-format :utf-8)))
        ;; now read from flexistream (which is set up by default as a character stream)
        (let ((buf (make-array 1024 :element-type 'character)))
          (loop for n = (read-sequence buf char-stream)
                while (< 0 n) do
            ;; coerce the buffer we read to a string and print it out
            (format t "GOT: ~s~%" (coerce (subseq buf 0 n) 'string)))))
      (lambda (ev)
        (format t "listener ev: ~a~%" ev))
      :stream t)))

(defun start-client ()
  (let* ((char-stream nil)
         (stream (as:tcp-connect "127.0.0.1" 8090
                   (lambda (sock data)
                     (declare (ignore sock data)))
                   (lambda (ev)
                     (format t "client ev: ~a~%" ev))
                   :stream t)))
    ;; here we write a string to the flexi-stream, which converts it to binary before sending
    (setf char-stream (flexi-streams:make-flexi-stream stream :external-format :utf-8))
    (write-sequence "Hello!" char-stream)))

(as:start-event-loop
  (lambda ()
    (start-server)
    (start-client))
  :catch-app-errors t)

Once started, the server will receive the "Hello!" string. This gets converted to binary when it's sent by the client, and converted back to a sequence of characters once the server reads it through the flexi stream (converted to a string via coerce).

Alternatively (and more concisely) in the client, you can just specify (tcp-connect ... :data "Hello") which will also convert that string to binary before sending it out via babel (a great octet/character conversion library), but for this example I wanted to show the process from character stream -> character stream.

nixz commented 11 years ago

Thank you. This helped.