[plt-scheme] Questions about client-server example (synchronizing write, reading sexpr safely, futures, and more)

From: Erich Rast (erich at snafu.de)
Date: Mon Jul 9 10:55:34 EDT 2007

Hi,

I've got some questions about the toy sample below. It has a few  
obvious deficiencies with which I'll deal later, e.g. the client just  
ceases to work when it raises an exception. Since this is ultimately  
intended for use in a real-world project, there is a few issues about  
which I'd like to hear some advice:

1. The writing of replies is threaded and delayed randomly in order  
to test asynchronous replies (i.e. client sends A, then B and  
receives reply-to-B before of reply-to-A). That means that I should  
make writes atomic, shouldn't I? How? I've looked up write-special- 
evt in the help desk, but can't figure out how to use it. Or is the  
normal write already atomic for ports created by make-pipe? What  
about TCP?

2. Would it be possible to implement client calls to the server that  
work like this:

(+ 10 (call '(rand 10)))

==> 16 ; or something else

(+ 10 (call '(rand 0)))

==> server error: random: expects argument of type <exact integer in  
[1, 2147483647]

I guess this time I really want futures, but how do I get them?

3. Is the use of apply in the sample code safe? I can't see why not,  
but want to be sure.

4. Can the reader be used in a real-world, TCP-based client-server  
implementation or does this pose security risks? Blocking doesn't  
seem to be a problem, since in the end there will be a guard that  
kills an inactive client connection after some time, but I wonder  
about other risks. Do I need special input validation or a customized  
reader? I'd much prefer sexprs over xml, but if anyone can convince  
me of the opposite, please go ahead.

5. Does anyone have a similar client-server example with  
'asynchronous reply handling' to look into? Is there a library that  
already does what I want (passing messages from A to B and handling  
replies from B in any order received and asynchronously)?

With best regards,

Erich

;;; -------------------------------------------------------------
;;; ----- CLIENT-SERVER EXAMPLE ---------------
;;; -------------------------------------------------------------
;;; just uses pipes for now
;;;  to run, call (test) and wait for output

(module server-test mzscheme

   (require (lib "date.ss"))

   (define (today)
     (date->string (seconds->date (current-seconds))))

   (define (now)
     (let ((t (seconds->date (current-seconds))))
       (string-append (number->string (date-hour t)) ":" (number- 
 >string (date-minute t)) ":" (number->string (date-second t)))))

   (define-values  (from-B to-A) (make-pipe))
   (define-values (from-A to-B) (make-pipe))

   (define new-id
     (let ((counter 0))
       (lambda ()
         (set! counter (+ counter 1))
         counter)))

   (define *sent* (make-hash-table))

   (define send
       (lambda (dest message cont)
         (let ((cmd-id (new-id)))
           (hash-table-put! *sent* cmd-id cont)
           (write (list cmd-id message) dest))))

   (define (->server msg cont)
     (send to-B msg cont))

   (define (reply dest message)
     (thread
      (lambda ()
        (sleep (random 30)) ; for testing async command-reply
        (write message dest))))

   (define (make-server name in-port)
     (thread
      (lambda ()
        (let loop ((in (read in-port)))
          (unless (eof-object? in)
            (display name)(print in)(newline)
            (with-handlers ((exn:fail:contract?
                             (lambda (exn) (reply to-A (list (car in)  
(list 'error (string-append (exn-message exn) (format ", in  
'~a'"  (cadr in)))))))))
              (case (caadr in)
                ((time) (reply to-A (list (car in) (list 'ok (apply  
now (cdadr in))))))
                ((date) (reply to-A (list (car in) (list 'ok (apply  
today (cdadr in))))))
                ((rand) (reply to-A (list (car in) (list 'ok (apply  
random (cdadr in))))))
                (else (reply to-A (list (car in) (list 'error (format  
"illegal command: '~s'" (caadr in))))))))
            (loop (read in-port)))))))

   (define (handle-reply input)
     (case (caadr input)
       ((ok) (let ((callback (hash-table-get
                              *sent*
                              (car input)
                              (lambda () (error 'handle-reply  
"illegal reply value or internal error; the command was never sent,  
in: ~a" input)))))
               (hash-table-remove! *sent* (car input))
                (apply callback (cdadr input))))
       ((error) (error 'handle-reply "server returned an error:  
~a" (cdadr input)))
       (else (error 'handle-reply "illegal or unsupported server  
reply: ~a" input))))

   (define (make-client name in-port)
     (thread
      (lambda ()
        (let loop ((in (read in-port)))
          (unless (eof-object? in)
            (display name)(print in)(newline))
          (if (number? (car in))
             (handle-reply in)
             (begin (display (format "client command handling not  
supported or illegal reply: '~a'" in))(newline)))
          (loop (read in-port))))))

   (define rec:from-A (make-server "\nServer B received from client  
A: " from-A))
   (define rec:from-B (make-client "\nClient A received from server  
B: " from-B))

   (define (test)
     (->server '(time) (lambda (t) (display t)(newline)))
     (->server '(date)(lambda (d)(display d)(newline)))
     (->server '(rand) (lambda (r)(display r)(newline)))
     (->server '(rand) (lambda (r)(display r)(newline))))
   )



Posted on the users mailing list.