Home > Clojure, Programming > Coffee Time with Java – Part 1.5 – Sockets in Clojure

Coffee Time with Java – Part 1.5 – Sockets in Clojure

It is almost a year now since I wrote the first part of meant-to-be series of topics concerning Java. I understood that there is no need to write the same stuff over and over again while there is plenty of it on the Net. But this time I’ve got an idea to rewrite that primitive server-client demonstration into Clojure and see how it will look like.
So all we need now is working Clojure REPL. You can just download the necessary jar-file, or use the Eclipse-plugin, actually there are a lot of ways to develop in Clojure as in any other language. I prefer Emacs+SLIME variant.
So lets start with a server, just like the last time. The declaration begins with specifying the namespace and imports.

(ns coffee-time.server)

(import '(java.util StringTokenizer)
        '(java.net ServerSocket Socket SocketException)
        '(java.io PrintWriter InputStreamReader BufferedReader))

Next we have to define the following global variables (presented as atoms):

(def storage (atom {}))
(def exit? (atom false))

Variable storage will be our hashmap that stores key-value pairs. Variable exit? is just a flag indicating if server should keep running.

(def parsing-re #"([A-Z]+)\s*(\S+)\s*(\S*)")

Variable parsing-re is the regular expression that will break the request to our server into separate tokens – command, key (if exists) and value (if exists).

(defn accept-connection [server-socket]
  (try (. server-socket accept)
       (catch SocketException e)))

This little function takes the ServerSocket as an argument, accepts the client connecting to it and returns the socket interacting with a client.
Note: in this entry I won’t explain the concepts of sockets and how they interact. For this information you can check the original entry.

(defmulti operate (fn[command & _]
                    (keyword command)))

Now this gets interesting. Here we define a multimethod – a method with an arbitrary dispatch function, which we define right here. The dispatch function does nothing but takes any number of arguments and returns the keyword from the first of arguments.

(defmethod operate :GET [c key & _]
           (if (contains? @storage key)
             (str key " = " (@storage key))
             "No such key found"))

(defmethod operate :PUT [c key value & _]
           (do (swap! storage assoc key value)
               (str "Value of " key " added")))

(defmethod operate :DELETE [c key & _]
           (if (contains? @storage key)
             (do (swap! storage dissoc key)
                 (str "Value of " key " deleted"))
             "No such key found"))

(defmethod operate :EXIT [& _]
           (reset! exit? true))

(defmethod operate :default [& _]
           "Wrong command")

Four functions above are the implementations for our multimethod. What you see next to the method name is a dispatch value. When we call the method operate on some arguments, Clojure applies the dispatch function
on them, which as we now know, returns the keywordized first argument. Then Clojure seeks a multimethod implementation with this value in its definition.
Multimethods are a beautiful technique for implementation decoupling. Whenever we decide to add more commands to our server we have just to add the appropriate methods. Actually the function overloading technique is just a multimethod with dispatch function that returns types of its arguments. Multimethods in general allow much more than that.
Note the last method in the previous snippet. It has the dispatch value :default. This means that when the result of the dispatch function doesn’t match any of existing methods then this default method will be invoked.

(defn parse-and-operate [s]
  (->> s
       (re-find parsing-re)
       (apply operate)))

With multimethods the original parse-and-operate function is extremely short and concise. The operator ->> is actually a macro that takes a value as an argument and then consequently applies all the forms to it, placing the value at the tail of the form. So this expression should be read as: Take the value s, then find the regular expression in it, drop the first item from the result and then apply the function operate to what has left.

(defn start-server [port]
  (with-open [s-socket (new ServerSocket port)
              c-socket (accept-connection s-socket)
              in (new BufferedReader
                      (new InputStreamReader (. c-socket getInputStream)))
              out (new PrintWriter (. c-socket getOutputStream) true)]
    (do (reset! exit? false)
        (while (not @exit?)
          (->> (. in readLine)
               (. out println))))))

Here we introduce one more awesome macro – with-open. It takes a group of variables that are bound to some resources and at the end of the macro with-open automatically calls .close on this resources even if the exception was raised. So this macro completely replaces the try..finally block that is often used in such cases.
What about the function itself? It is pretty self-explanatory. It creates a new ServerSocket, gets the client Socket from the function accept-connection that we defined above, binds input and output streams, and then repeatedly passes the incoming message from client to the function parse-and-operate.

So, that’s the whole server! Its size is only 53 lines of pretty “vertical” and extendable code. For sure, this code is rather proof of concept than the real code you would write, but the result is still impressive.

So now to the client part. This time I won’t use System.in stream for interaction with user, I decided to use the power of REPL instead. But first come the declarations:

(ns coffee-time.client)

(import '(java.net Socket)
        '(java.io PrintWriter InputStreamReader BufferedReader))

(def socket (atom nil))
(def in (atom nil))
(def out (atom nil))

Nothing interesting here. socket is used to store Socket object, in and out speak for themselves.

(defn start-client [port]
      (reset! socket (Socket. "localhost" port))
      (reset! out (PrintWriter. (. @socket getOutputStream) true))
      (reset! in (BufferedReader. (InputStreamReader. (. @socket getInputStream))))))

This function tries to connect to server (hardcoded localhost) on the specified port and gets necessary streams.

(defn send-message [m]
    (. @out println m)
    (println (. @in readLine))))

Again pretty simple – the function gets the message as an argument, passes it to the output stream, gets response from input stream and prints it on the screen.

(defn stop-client []
  (do (. @in close)
      (. @out close)
      (. @socket close)))

We should invoke this last function to tidy everything up after we did our work.

Once again, the client lacks a lot of try/catch cases because I didn’t want to bloat the code and since this is toy problem, clarity > robustness.

Now, after both server and client are done, you can test them. First, in one repl, execute:

coffee-time.server> (start-server 1234)

In another repl, run:

coffee-time.client> (start-client 1234)

and then you can send messages to the server as following:

coffee-time.client> (send-message "PAT message Hello world!")
Wrong command
coffee-time.client> (send-message "PUT message Hello world!")
Value of message added
coffee-time.client> (send-message "GET message")
message = Hello world!
coffee-time.client> (send-message "DELETE message")
Value of message deleted
coffee-time.client> (send-message "GET message")
No such key found

Thus, we ported our Java simple client-server application to Clojure. The code has less noise in it, more expressive, understandable and accurately put together. There is still much left to be done – you can introduce a few macros to eliminate code duplication, break the code into few more functions, but it is now up to you what to do.
I hope, this topic was somehow helpful to you. If you want to learn Clojure more, you can start with the book of Luke VanderHart and Stuart Sierra “Programming Clojure” or “The Pragmatic Bookshelf Programming Clojure” by Stuart Halloway. For deeper Clojure understanding I recommend reading an awesome book “The Joy of Clojure” by Michael Fogus and Chris Houser.
Thank you for your attention.

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: