Common Lisp Beginner


Home
Links Suggestions About

Sockets
Introduction
   In this part of the tutorial, we'll create two programs to demonstrate socket programming under Common Lisp. The first program will simply make a HTTP request to retrieve a web page, and the second program will be a server that gives us the time whenever we connect to it.
    Different Common Lisp implementations support their own unique socket API. If you look at the SBCL socket API and the CLISP socket API, you'll quickly realize that they are two different interfaces. Such differences are an obstacle to portability. As a result, people have created libraries which abstract the differences, and provide a socket portability layer for your code. One such library is called usocket. usocket runs on many Common Lisp implementations, such as SBCL, CMUCL, CLISP, and many others, however we'll be using usocket with SBCL. Before we begin, please install usocket. Refer to previous tutorials on installing libraries using ASDF-INSTALL, or visit another introductory tutorial.

Client
 
    The purpose of this client is to connect to the Google web server, and retrieve the main web page. The first step is to create a socket, which represents our connection to the Google web server. The web server is located at google.com on port 80.

1: (require :usocket)

2: (setq sock (usocket:socket-connect "google.com" 80))

    Line 1 imports the usocket library. Line 2 creates a connection to the google web server. The return value of usocket:socket-connectis an object called stream-socket. This object is our handler to the connection. We'll now write several HTTP headers to the socket, so that the web server sends us the main web page.

3: (format (usocket:socket-stream sock) "~A~C~C~A~C~C~C~C"
                                        "GET /index.html HTTP/1.1"
                                        #\Return #\Newline
                                        "Connection: close"
                                        #\Return #\Newline
                                        #\Return #\Newline)
4: (force-output (usocket:socket-stream sock))

     When reading or writing to a socket, most of the time you probably do not want to read or write a specified number of bytes. Instead, you would like to treat a socket as if it were a file, and read and write to it as if it were a stream. If we can convert a socket to a stream, then we can use Common Lisp's stream functions to work with sockets. usocket:socket-stream does exactly that. It takes a socket, and returns a stream. On Line 3 we wrote a HTTP GET request to the server using format, which we can do since format is capable of writing to streams. Line 4 forces the output to be sent, in case it is still being buffered. Very often, your machine will buffer input and output for effeciency reasons, and wait until a certain amount of data accrued before it is actually sent out. In our case, we simply want to send whatever we have, and that's it.
    Once we sent a GET request, we must now read the content of the socket stream, in case the server has responded. If the server has not yet respondend, then the read call will block until data is available, or the connection is broken.

5: (do ((line                                                            
         (read-line (usocket:socket-stream sock) nil)                       
         (read-line (usocket:socket-stream sock) nil)))                     
       ((not line))                                                      
      (format t "~A" line))

    On Line 5, we keep reading from the stream until we reach nil. Please not that we're using Common Lisp's read-line function to read from a socket stream, just like you'd read data from a file. Also, note that if the data has still not been sent by the server, the call to read-line will block.
    The output of the reading the stream should be similar to this:

HTTP/1.1 200 OK^MDate: Tue, 16 Feb 2010 07:20:22 GMT^MExpires: -1^MCache-Contro
l: private, max-age=0^MContent-Type: text/html; charset=ISO-8859-1^MSet-Cookie:
 PREF=ID=a31df38e4c117a79:TM=1266304822:LM=1266304822:S=XbYHZ_UaMges-ego; expir
es=Thu, 16-Feb-2012 07:20:22 GMT; ...

    We're only showing the beginning of the output, since the actual output is much larger. We've sucessfully created a client. Now let's move on to creating a server.

Server
Soon to be created...