Echo UDP server in Clojure

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.

Terminal window
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.

Terminal window
nc -u 127.0.0.1 1230
hello
HELLO

Java classes

The Java standard library has the package java.net. Inside it we can find these three classes that I used:

InetAddress

It represents an IP address.

DatagramSocket

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 packet
  • receive(DatagramPacket p): receives a datagram packet
  • bind(SocketAddress addr): binds the socket to a local address
  • close(): closes the socket

In 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.

DatagramPacket

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.

Linux socket interface

Terminal window
man socket
Back to notes