Crossing the streams - Land of Lisp chapter 12 - Using SBCL
Previously, I wrote on how to get Chapter 7 of Land of Lisp working in SBCL. To recap my experience with the book, so far it has simply taken the tweak of one line of code, and the rest of the examples have worked straight from the page to the REPL.
Chapter 12 introduces working with streams. It starts by dealing with file read/writes, then ups the ante a little and gets you to create a socket connection between a server and a client. Again, the book posts simple, elegant example code, though it the socket code is not portable to SBCL.
Page 245 offers two libraries cl-sockets, and usocket which attempt to standardise sockets on Common Lisp. After researching them both, I chose to base my solution on usocket for two reasons:
- It is used in the hunchentoot web server, which seems to be the most prevalent webserver in Common Lisp.
- It is available through Quicklisp.
If you haven't used Quicklisp before, it is an elegant Common Lisp package manager that handles a wide number of libraries (or at least a lot of the ones I've seen in tutorials). My previous attempts at learning the language have all hit the wall over my ineptitude with package management. Quicklisp solved that problem for me, and seems to be idiot (me) proof. If you choose to use it, once installed, loading usocket should be as simple as typing in the command:
(ql:quickload "usocket")
Once you have usocket we can begin. The API documentation can be found at http://common-lisp.net/project/usocket/api-docs.shtml
Following the examples in the book, there seems to be three parts to getting up and running:
- Specify a socket to use on the "server"
- Set up a process on the server which listens out on this socket for any action
- Set up a process on the client which connects to the socket on the server
To accomplish the first part, which specifies port 4321 to use on localhost "127.0.0.1", I used this code:
(defparameter my-socket (usocket:socket-listen "127.0.0.1" 4321))
I will interject a thought at this point. Earlier in the book we are given strict instruction to follow convention and use "earmuffs" on global variables. That means that my-socket should be declared *my-socket*. However, in Chapter 12, the code examples do not have * *. I'll follow the books example here, though I've got a suspicion this is one of those earmuff moments.
Once we have a socket declared, we need to set up a process to work with it. From reading the API, I wasn't really sure whether I should use socket-accept, or socket-listen. I chose socket-accept, which worked as I had hoped
(defparameter my-stream (usocket:socket-accept my-socket))
The REPL may wait at this point, and listen for a connection on this port. Lets set one up on the client side
You'll need to open a new REPL in another window. (I'm using emacs/slime, but this should all work from the terminal too. Note that if you will need to ensure you have usocket loaded into this new instance of the REPL, as above.
Once your client REPL is loaded, you need to create the process that connects to the port on the server REPL
(defparameter my-stream (usocket:socket-connect "127.0.0.1" 4321))
Now we have a processes on both the client and server, connecting to port 4321 on of "127.0.0.1". We can send our message
From the client REPL, (following the examples in the book)
(print "Yo, Server!" (usocket:socket-stream my-stream)) (force-output (usocket:socket-stream my-stream))
Note that we have to use (force-output). This is because with usocket, the stream is buffered. It will not necessarily send just by printing to it, you must explicitly tell it to do so.
On the server REPL:
(read (usocket:socket-stream my-stream)) ;; "Yo, Server!"
Remaining with the server
(print "What up, Client!" (usocket:socket-stream my-stream)) (force-output (usocket:socket-stream my-stream))
Then check whether this has been sent to the client REPL:
(read (usocket:socket-stream my-stream)) ;; "What up, Client!!"
Once you are finished with the connections, you need to close the connections, by issuing the following command at both REPL's
(usocket:socket-close my-stream)
As you can see, issuing these commands becomes rather cumbersome, so I've hacked together a couple of functions, (stream-read) and (stream-print)
(defun stream-read (stream) "Reads from a usocket connected stream" (read (usocket:socket-stream stream))) (defun stream-print (string stream) "Prints to a usocket connected stream" (print string (usocket:socket-stream stream)) (force-output (usocket:socket-stream stream)))
For stream read pass the stream as a parameter and it should return the current stream value.
(stream-read my-stream)
Stream print requires a message, and the stream to send it to.
(stream-print "Hello!" my-stream).
The next chapter in the book deals with creating a webserver. I'm guessing it may also require a work around, if I manage to figure it out, this "series" might continue. :)
I'm also learning to use github, so for those who want it, I created two scripts "server.lisp" and "client.lisp" containing the code above to get the messaging system up and running. The repository is available at https://github.com/ciaranbradley/land-of-lisp-chap-12-usocket
