Conao3 Notes

Home

ClojureでEcho Server

2024-12-23

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

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

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

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

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

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

調査

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

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

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

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

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

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

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

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

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

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

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

実装

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

(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らしく書かせて欲しいという感情はありますが、とりあえずこれでできました。