Previous: Creating the Client, Up: Quick Start


3.0.3 Creating the Server

Creating a TCP server for our RPC interface should be quite easy as well. We can re-use our previous type definitions (see Defining Data Types). Then, all we have to do is to create a definition for our program.

     (use-modules (rpc rpc server))
     
     (define (split-number-handler number)
       ;; Handle a `split-number' request.
       (let* ((int (floor number))
              (dec (floor (* 1000 (- number int)))))
         (list (inexact->exact int)
               (inexact->exact dec))))
     
     (define my-rpc-program
       ;; Define our RPC program.
       (let* ((proc    (make-rpc-procedure 1 xdr-double result-type
                                           split-number-handler))
              (version (make-rpc-program-version 0 (list proc))))
         (make-rpc-program 80000 (list version))))

Alternatively, using the compiler allows the above definition of my-rpc-program to be automatically generated from the XDR/RPC definition file (see grpc-compile):

     $ grpc-compile --server < input.x > server.scm

However, there is a slight difference compared to the above “hand-written” approach: server.scm does not contain the actual definition of my-rpc-program since it does not know about your split-number-handler procedure. Instead, given the RPC/XDR definition given earlier, it contains a make-ARITHMETIC-PROGRAM-server procedure; this procedure can be passed a list associating RPC names to Scheme procedures, and returns the resulting RPC program object:

     (define my-rpc-program
       (make-ARITHMETIC-PROGRAM-server
         `(("ARITHMETIC_VERSION"  ;; specify the target version
     
             ;; list all supported RPCs for this version
             ("split_number" . ,split-number-handler)))))

As can be seen, using the compiler-generated server stub, one doesn't have to deal explicitly with program, version and RPC numbers, which clarifies the code.

Finally, we can make the server listen for incoming connections and handle client requests, using Guile's networking primitives.

     ;; Creating a listening TCP socket.
     (define server-socket (socket PF_INET SOCK_STREAM 0))
     
     ;; Listen for connections on 127.0.0.1, port 6666.
     (bind server-socket AF_INET INADDR_LOOPBACK 6666)
     (listen server-socket 1024)
     
     ;; Go ahead and serve requests.
     (run-stream-rpc-server (list (cons server-socket my-rpc-program))
                            1000000 ;; a one-second timeout
                            #f      ;; we don't care about closed connections
                            (lambda () ;; our idle thunk
                              (format #t "one second passed~%")))

And now we're all set: We have a working TCP client and server for this wonderful RPC interface! This would work similarly for other stream-oriented transports such as Unix-domain sockets: only the socket and bind calls need to be adapted.