An echo server

Our first and simplest example is an echo server. Each incoming payload is repeated.

Our protocol will be simple and limited:

  • Connections are made to TCP port 1337.
  • Messages are line delimited.
  • Messages are played back

To do this with net, we will create what’s called a Netty ChannelAdapter, which will sit at the bottom end of our input pipeline:

+--------------------------+
|                          |
|  | line frame decoder    |
|  v                       |
+--------------------------+
|                          |
|  | string decoding       |
|  v                       |
+--------------------------+
|                        ^ |
|    string encoding     | |
|                          |
+--------------------------+
|                        ^ |
|    line frame encoder  | |
|                          |
+--------------------------+
|                          |
|    our chat handler      |
|                          |
+--------------------------+

This onion approach to adapters allows us to create a handler, knowing that payload will come as a well formed string.

We can start by creating our namespace:

(ns server.echo
  (:require [net.tcp         :as tcp]
            [net.ty.channel  :as channel]
            [net.ty.pipeline :as pipeline]))

Next we will create our echo pipeline:

(defn pipeline
  []
  (pipeline/channel-initializer
    [(pipeline/line-based-frame-decoder)
     pipeline/string-decoder
     pipeline/string-encoder
     pipeline/line-frame-encoder
     (pipeline/with-input [ctx msg]
       (channel/write-and-flush! ctx msg))]))

As you can see, our pipeline definition closely mimicks what we described above, and all steps of the pipeline but the last are provided for us:

The pipeline creation is wrapped in a call to pipeline/channel-initializer, to create a ChannelInitializer out of it. The role of initializers is to create an instance of the handling finite state machine for each incoming connection.

In our custom handler, we are given a ChannelHandlerContext and the input message. The context is Netty’s representation of the connection and may be used to write to it (through channel/write!, or channel/write-and-flush!).

Last, we can start our server:

(tcp/server {:handler (pipeline)} "localhost" 1337)

To test this example server, you may run lein run -m server.echo within the net project.