Development Planning
First Iteration
- Take a slightly different approach to Java wrapping than what clj-sockets and async-sockets have done:
- Follow the Java class taxonomy closely for low-level protocols and implementations
- Provide a higher-level, more deveoloper-facing (developer-friendly) API
- Take inspiration from:
- 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
- Read the Java docs carefully
- Document a Java example of creating:
- A UDP socket server
- A UDP socket client
- See Writing a Datagram Client and Server
- Sketch out how this would be matched in Clojure
- Check out these:
- Echo server with core.async
- async-sockets socket servers
- core.async talk - see the section of code titled “Limited Access to a Shared Resource”
- Wrap the necessary Java classes
- Provide a working example in Clojure of client and server
- 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
- 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