echo server
Programming Clojureを読み終えた。
Common Lispとは微妙に違うので、コードを書く時にとまどう事も多いが、豊富なJavaライブラリを直接使用できるのは大きな利点である。
emacs + slimeを使えば、動作確認をしながらコードを書けるので、サクサク開発できて、なかなか便利である。
一通りの機能は理解できたので、いろいろ書いてみようと思う。
ネットワーク関係が面白いので、まずはecho serverを作ってみた。
sample/echo.clj
(ns sample.echo (:use [clojure.contrib.server-socket :only (create-server close-server)])) (def port-no 3000) (defn echo [in out] (let [caption (str "*echo(" (.getId (Thread/currentThread)) ") ")] (println (str caption "start")) (let [buf (make-array Byte/TYPE 256)] (loop [] (let [size (.read in buf)] (when (not= size -1) (.write out buf 0 size) (println (str caption "loop")) (recur))))) (println (str caption "end")))) (def echo-server (ref nil)) (defn start-echo-server [] (dosync (ref-set echo-server (create-server port-no echo)))) (defn stop-echo-server [] (close-server @echo-server) (dosync (ref-set echo-server nil)))
start-echo-serverを実行し、3000番ポートにtelnetすると、入力した文字列をそのまま返す。
サーバ側
user> (use 'sample.echo) nil user> (start-echo-server) {:server-socket #<ServerSocket ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3000]>, :connections #<Ref@18f127c: #{}>} *echo(73) start ←◆接続した *echo(73) loop ←◆hogeを送信 *echo(73) loop ←◆fugaを送信 *echo(73) end ←◆切断した *echo(74) start
クライアント側
satoshi@ubuntu:~$ telnet localhost 3000 Trying ::1... Connected to localhost. Escape character is '^]'. hoge hoge fuga fuga ^] telnet> quit Connection closed.
終了するには、stop-echo-serverを実行する。
Clojureでは、clojure.contrib.server-socketライブラリが用意されており、このようなサーバアプリを簡単に作成できる。
上記のサンプルのように、create-server関数にポート番号と、入力ストリームと出力ストリームを引数とする関数を渡すだけで、マルチスレッドサーバを書くことができる。
ソケットの入出力ではbyte配列を使用するが、byte配列を作るにはmake-array関数を使う。
make-array関数に渡すtype引数は、byteプリミティブ型の場合、Byte/TYPEを指定する。
ちなみに、intの場合はInteger/TYPEを指定する。
例) 長さ10のbyte配列を作る。
(make-array Byte/TYPE 10)
上記と同様なecho serverのJavaによる実装は以下の通り。
sample/echo/EchoServer.java
package sample.echo; import java.io.IOException; public class EchoServer { private static final int PORT_NO = 3000; public static void main(String[] args) throws IOException { EchoServer echoServer = new EchoServer(); echoServer.start(); } private void start() throws IOException { ServerSocket serverSocket = new ServerSocket(PORT_NO); System.out.println(String.format("*ポート番号%dで待機しています。", PORT_NO)); while (true) { Socket socket = serverSocket.accept(); System.out.println(String.format("*接続しました(%s:%d))。", socket .getInetAddress(), socket.getPort())); Thread echoThread = new Thread(new Echo(socket)); echoThread.start(); } } }
sample/echo/Echo.java
package sample.echo; import java.io.IOException; public class Echo implements Runnable { private Socket socket; public Echo(Socket socket) { this.socket = socket; } @Override public void run() { final String caption = String.format("*echo(%d) ", Thread.currentThread().getId()); System.out.println(caption + "start"); try { InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); int size; byte[] buf = new byte[256]; while ((size = in.read(buf)) != -1) { System.out.println(caption + "loop"); out.write(buf, 0, size); } socket.close(); System.out.println(caption + "end"); } catch (IOException e) { e.printStackTrace(); } } }
12/13追記
おぉ、いかん。socket.close()をfinallyにしていなかった。