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にしていなかった。