Development Planning

First Iteration

  1. Take a slightly different approach to Java wrapping than what clj-sockets and async-sockets have done:
  1. Support both TCP and UDP sockets
  • Other libraries have focused on supporting TCP primarily
  • We have a need for UDP servers
  • We’ll focus there first, then then catch up with TCP later
  1. Read the Java docs carefully
  1. Document a Java example of creating:
  1. Sketch out how this would be matched in Clojure
  1. Wrap the necessary Java classes
  2. Provide a working example in Clojure of client and server
  3. Update the example (or create a new one) that would show instead how this would be done ideally in Clojure
  • Possibly wrap the Clojure wrapper in a higher-level namespace
  • Provide a new example that’s simpler to use than the example that is a straight translation of the Java example
  1. Introduce command channels with core.async

Clojure Sketch

A pre-implementation imagining of creating UDP servers

(ns examples.udp.echo-server.server
  (:require
    [clojure.core.async :as async]
    [clojure.java.io :as io]
    [inet.address :as inet]
    [sockets.datagram.packet :as packet]
    [sockets.datagram.socket :as socket]))

(def max-packet-size 4096)
(def default-port 15099)

(defn str->bytes
  [text]
  (byte-array (map byte text)))

(defn bytes->str
  [data]
  (new String data))

(defn get-port
  [port]
  (case port
    nil default-port
    (Integer/parseInt port)))

(defn echo-service
  "For any in-coming message, simply return the same data."
  [in out]
  (async/go-loop []
    (let [dest (async/<! in)]
      (async/>! out dest)
      (recur))))

(defn packet-reader
  [sock]
  (let [in (async/chan)]
    (async/go-loop []
      (let [pkt (socket/receive sock max-packet-size)]
        (async/>! in {:remote-addr (packet/address pkt)
                      :remote-port (packet/port pkt)
                      :data (bytes->str (packet/data pkt))}))
      (recur))
    in))

(defn echo-writer
  [sock]
  (let [out (async/chan)]
    (async/go-loop []
      (let [msg (async/<! out)
            pkt-text (format "Echoing: %s\n" (:data msg))
            pkt-data (str->bytes pkt-text)
            pkt (packet/create pkt-data
                               (count pkt-data)
                               (:remote-addr msg)
                               (:remote-port msg))]
        (socket/send sock pkt))
      (recur))
    out))

(defn -main
  "You can start the server like this:
  ```
  $ lein run -m examples.udp.echo-server.server
  ```

  To connect to the server:
  ```
  $ nc -u localhost 15099
  ```

  Then type away, and enjoy the endless echo chamber ;-)"
  [& [port & args]]
  (println "Starting server ...")
  (let [sock (socket/create (get-port port))]
    (println (format "Listening on udp://%s:%s ..."
                     (inet/host-address (socket/local-address sock))
                     (socket/local-port sock)))
    (async/go
      (echo-service
        (packet-reader sock)
        (echo-writer sock)))
    (.join (Thread/currentThread))))

Second Iteration

TCP

Third Iteration

SSL

Fourth Iteration

java.nio