Hatena::Groupocaml-nagoya

yoshihiro503の関数的日記

2011-10-15 (Sat)

Androidを簡易的な対話サーバーにしよう

| 21:23 |  Androidを簡易的な対話サーバーにしよう - yoshihiro503の関数的日記 を含むブックマーク はてなブックマーク -  Androidを簡易的な対話サーバーにしよう - yoshihiro503の関数的日記

実装

java.net.ServerSocketを使えば簡単なソケットサーバーAndroidアプリとして実装できる。

今回はScalaを使って純粋関数的な一行ずつ読む対話的サーバーを実装してみた。Scalaを使うことによって驚くほど簡単に実装でき、かつ単体テストが細かくできるようになる。サーバーロジック部分は次のような入力行の無限列(Iterator[String])をもらって応答の列(Iterator[String])を返すという純粋関数記述した。ロジック部分とサーバーとしての入出力を切り分けることによって、ロジックだけの単体テストが非常に簡単になる。

object Interpreter {
  def lineInterpreter(lines : Iterator[String]): Iterator[String] = {
    lines takeWhile {
      //exitが来たら応答はおしまい
      line => line != "exit"
    } filter {
      //空行は無視
      line => line != ""
    } map {
      //入力に対してFoo:をくっつけるだけの簡単なお仕事
      line => "FOO: "+line
    }
  }
}

これを使う側は次のような感じ。

  def server() = {
    import java.net.ServerSocket
    import java.net.Socket
    import java.io._
    def using[T](op : (InputStream,OutputStream) => T) : T = {
      val server = new ServerSocket(port) {
        setSoTimeout(0)
      }
      val socket = server.accept()
      val in =  socket.getInputStream()
      val out = socket.getOutputStream()
      def close() = { server.close(); in.close(); out.close() }
      try {
        val y = op(in, out)
        close(); y
      } catch {
        case (e: Exception) => { close(); throw e }
      }
    }
    using {
      case(in, out) =>
        val writer = new PrintWriter(out)
        Interpreter.lineInterpreter(getLines(in)).foreach {
      	  res =>
	    writer.println(res)
	    writer.flush()
        }
    }
  }

  def getLines(in : java.io.InputStream) : Iterator[String] = {
    import java.io._
    val reader = new BufferedReader(new InputStreamReader(in))
    Iterator.continually(reader.readLine()).takeWhile(_ != null)
  }

getLines関数javaのInputStreamを行の遅延リストにしているのがポイントだ。この技は@ScalaTohokuさんに教えてもらった。

あと、スレッドを作る必要があったのでActorも少しだけ使ってみた。

ソースコードを含むプロジェクトはこちら:

https://github.com/yoshihiro503/AndroidSocketServer

使い方

APKファイルを以下からダウンロードしてAndroidに入れる。

https://github.com/yoshihiro503/AndroidSocketServer/downloads

Android側で起動するとIPアドレスが表示され、サーバーが起動するので別の端末からncコマンドなどでアクセス

 $ nc [AndroidのIPアドレス] 8080

入力した行に対してFoo: がくっついていたら成功!空行は無視され、終了する場合exit入力する。

ビルド

自分ビルドしてみたいという人はsbt-0.11.0とSBTのandroid-pluginを用意しよう。それらがあればビルドや実行は非常に簡単だ。

 $ sbt android:package-debug

実機にインストール

 $ adb install -r target/socketserver-0.1.apk