nb contains code for non-blocking I/O functions for picoLisp.
As part of the nb code, there are a few C functions to enable non-blocking i/o in picoLisp:
(eagain) -> 'cnt
Return the value of errno -EAGAIN (as negative number) which "roughly" says that the data/file descriptor is not ready for i/o.
(block 'any 'flg) -> 'flg
Set the socket any to blocking (flg=T) or non-blocking (flg=NIL) mode.
(rdx 'lst 'cnt1 ['cnt2]) -> 'cnt|NIL
Read cnt1 number of bytes into the list lst starting from cnt2 element. Return number of bytes read, -errno (as negative number) on error.
(wrx 'lst 'cnt1 ['cnt2]) -> 'cnt|NIL
Write cnt1 bytes from the list lst starting from cnt2 element. Return number of bytes written, -errno (as negative number) on error.
There are a few examples of asynchronous servers using non-blocking i/o as part of the part of nb code.
To run the server, load the file chat.l and call (chat 4444). The server will run in background so your REPL will still be available. Then connect a few clients, e.g.
$ telnet localhost 4444
Lets discuss the implementation details.
The server handles requests coming on port 4444 and creates an asynchronous handler for each accepted request. If you type something in one client, the message will get sent to all connected clients.
So we need to keep track of all connected clients (list of handlers in *H variable).
(off *H)
Then we define our asynchronous handler. I settled for a class but it could be a closure or simply functions with the necessary data (socket and write queue) kept in the elements of *H list.
(class +Handler) # s [w] (dm T (S) (=: s S) (=: w (new)) (push '*H (cons S This)) ) (dm rm> () (prinl "q " (: s)) (task (: s)) (close (: s)) (setq *H (delq (assoc (: s) *H) *H)) )
The constructor T initializes the handler and adds it into the *H list. The method rm> closes the socket and removes the handler from the *H list.
Next, we need to handle non-blocking i/o. This is the tricky bit. We do not write the output messages directly as we would do with normal blocking i/o functions but we must write them in chunks that a client is able to read immediately (without blocking our server).
The actual write is deferred till a suitable moment. Until then, messages wait in a FIFO queue of each handler (client).
(dm wr> (Who Msg) (fifo (: w) (cons Who Msg)) )
The cb> method is the callback handling events on client sockets. If a message arrives on a client socket, the data is read in and put into write queues of all handlers. The handlers then tries to write all queued messages to the connected clients.
To avoid generating too much unnecessary garbage, we have a fixed size buffer shared by all handlers. Note that we are running the server in one process only so no locking or synchronization is ever required.
(setq *N 1024 *B (need *N)) # read buffer (dm cb> () (block (: s) NIL) (let N (in (: s) (rdx *B *N)) (prinl "r " (: s) " " N) (cond ((gt0 N) (for H *H (wr> (cdr H) S (head N *B)))) ((= N (eagain))) (T (rm> This)) ) ) (for H *H (fl> (cdr H))) ) (dm fl> () (use X (while (and (setq X (cadr (val (: w)))) # peek head (let (S (cdr X) M (length S) N (out (: s) (wrx S M))) (prinl "w " (: s) " " N "/" M) (when (gt0 N) (if (<= M N) (fifo (: w)) (set (cdr (val (: w))) (tail (- M N) S)) ) ) ) ) ) ) )
The fl> method does the actual writing of a message from the write queue of a handler. It writes all data the client is ready to read.
The last thing we need is to start up the server and accept incoming connections:
(de chat (Port) (task (port Port) (when (accept @) (task @ This (new '(+Handler) @) (cb> This) ) ) ) )
This page is linked from: picoLisp
Revisions: View source XHTMLV | RSSV
picoWiki pages can be edited by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively