This is an echo server that returns the input in uppercase. If I send hello
I receive back HELLO
.
(ns user (:require [clojure.string :as str]) (:import (java.net DatagramSocket InetAddress DatagramPacket)))
(defn create-udp-echo-server "Creates a UDP echo server on the given host and port. The received message is passed to the handler function and it's echoed back as a response. Returns an atom that can be used to start/stop the server later." [{:keys [host port handler]}] (let [stop-flag (atom false)] (future (with-open [socket (DatagramSocket. port (InetAddress/getByName "0.0.0.0"))] (println "UDP server started. Listening on port " port) (let [buffer (byte-array 1024)] (loop [] (when-not @stop-flag (let [request-packet (DatagramPacket. buffer (count buffer))] (.receive socket request-packet) (let [receive-msg (String. (.getData request-packet) 0 (.getLength request-packet))] (println "Received: " receive-msg) (let [response-msg (handler receive-msg) response-packet (DatagramPacket. (.getBytes response-msg) (count response-msg) (.getAddress request-packet) (.getPort request-packet))] (.send socket response-packet)))) (recur)))))) stop-flag))
;; Example usage(let [udn-echo-server (create-udp-echo-server {:host "localhost" :port 1230 :handler str/upper-case})] (Thread/sleep 10000) (println "Stopping UDP server") (reset! udn-echo-server true))
Nothing crazy going on. The atom, future, loop is how I managed it to keep running forever but have a way to stop it.
To test I can use nc
.
nc -u 127.0.0.1 1230
The above connects to the IP/Port over UTC. Then I can type something press Enter
and it shows the response.
nc -u 127.0.0.1 1230helloHELLO
The Java standard library has the package java.net
. Inside it we can find these three classes that I used:
It represents an IP address.
It represents a socket for sending and receiving datagram packets. It’s used for connectionless packet delivery.
Key methods:
send(DatagramPacket p)
: sends a datagram packetreceive(DatagramPacket p)
: receives a datagram packetbind(SocketAddress addr)
: binds the socket to a local addressclose()
: closes the socketIn my implementation I didn’t need to use bind
because the constructor already does the binding. I also didn’t have to close because I used with-open
which mimics Java’s try-with-resources.
It contains the data to be send or received, along with the source or destination IP address and port number.
Main constructors:
DatagramPacket(byte[] buf, int length
: Constructs a DatagramPacket for receiving packets.DatagramPacket(byte[] buf, int length, InetAddress address, int port)
: Constructs a DatagramPacket for sending packets.man socket