# ClojureでEcho Server

Date: 2024-12-23
URL: https://s.conao3.com/blog/2024/4d880f
Author: conao3 (Naoya Yamashita)

ClojureでTCPソケットを使ったプログラムを書きたくなりました。
TCPソケットを使おうと思ったら書くのがEcho Serverですね。書きましょう。

ライブラリなしで書くときはとりあえずcoreのドキュメントを参照します。
<https://clojure.github.io/clojure/index.html>

しかし、ソケット通信のためのAPIは用意されてないので、javaのドキュメントを参照します。
Clojureが利用しているJDKのバージョンは以下で調べることができます

```clojure
(System/getProperty "java.version")
;;=> "21.0.5
```

JDK21のドキュメントはこちらです。

-   <https://docs.oracle.com/javase/jp/21/index.html>
-   <https://docs.oracle.com/javase/jp/21/docs/api/index.html>

`java.net` を使うことで実装できそうです。

# 調査

まずサーバー側のソケットを作り、 `accept` で待ち受けます。
`accept` は通信要求が来るまでブロッキングされます。

```clojure
(def server-socket (java.net.ServerSocket. 15390))
;;=> #'user/server-socket

(def client-socket (. server-socket accept))
;;=> #'user/client-socket
```

なお、クライアントは `telnet` で試すことができます。
`telnet` は `C-]` を押すと通信を止めることができます。

```sh
$ telnet localhost 15390
Trying ::1...
Connected to localhost.
Escape character is '^]'.
```

サーバー側に戻ります。
`client-socket` を得ることができたら、 `io/reader` と `io/writer` により、ioを得ることができます。

```clojure
(def input-stream (io/reader client-socket))
;;=> #'user/input-stream

(def output-stream (io/writer client-socket))
;;=> #'user/output-stream
```

`io/reader` の返り値のため、 `readLine` などを利用することができます。

```clojure
(. input-stream readLine)
;;=> "asdf"
```

`readLine` は `"\n"` が入力されるまでブロッキングします。

# 実装

調査を受けて、実装はこちらです。

```clojure
(ns echo-server.core
  (:require
   [clojure.java.io :as io])
  (:import
   [java.net ServerSocket]
   [java.io BufferedReader BufferedWriter])
  (:gen-class))

(defn handle-client [client-socket]
  (let [input-stream ^BufferedReader (io/reader client-socket)
        output-stream ^BufferedWriter (io/writer client-socket)]
    (loop []
      (let [inpt (. input-stream readLine)]
        (println "Received: " inpt)
        (when (and inpt (not= "" inpt))
          (. output-stream write (str inpt "\n"))
          (. output-stream flush))
        (when-not (nil? inpt)         ; nil indicates EOF
          (recur))))))

(defn start-server []
  (println "Listening localhost:15390")
  (let [server-socket (ServerSocket. 15390)]
    (loop []
      (let [client-socket (. server-socket accept)]
        (println "Connection Accepted")
        (handle-client client-socket))
      (recur))))
```

もうちょっとClojureらしく書かせて欲しいという感情はありますが、とりあえずこれでできました。
