私ってば、javaでSocketプログラミングも書いたことのないペーペーなわけでして、
Socketプログラミングも知らずにプログラマを名乗れるのか!なんて声が天から聞こえてくる気がするので
いっちょやってみっか!などとドラゴンボールの主人公風なカンジでSocketプログラミングしてみるのです。

はじめに

雑な環境説明ですが、eclipseをつかいます。jdkは1.6です。

つくるもの

さて、Socketプログラミング、とはいうものの、つくるものが不明瞭ではにっちもさっちもいきません。
今回は最近ハヤリ(?)のKVS(key value store)サーバを作ってみようと思います。

といっても、世に出ているような大層な機能を盛り込むつもりは毛頭なくて、
単純にMapがひとつあり、それに対してgetとputとdeleteくらいをTCP/IPを介してアクセスできればいいかなー、程度のものですので、ご期待なさらないでください。
想定している用途としては「リアルタイムに外部から書き換え可能なプロパティファイル(のようなもの)」と思ってください。

まずはベースを

ネットで探して、パクってきます
java socket serverってカンジでぐぐって出てきた
3. サーバソケット | TECHSCORE(テックスコア)

このページを参考にします。というか、とりあえずソース(EchoServer.java)をまるまるコピペします。

職業プログラマはこのように、(権利上問題にならなそうな範囲で)パクることがままあります。
今回はライセンスが明記されてなさそうですが、入門のサンプルコードだし、見たカンジ創意工夫が認められそうなコードでもない(=著作権云々で訴えられる可能性が低い)ので堂々とパクります。

さておき

eclipseでプロジェクト作ります。
javaでつくるkvsのserverなのでプロジェクト名は jkvss にしましょう。
んでパッケージは net.yosioo.jkvs で。最初に作るサーバ用のクラスは JkvsServer です。
クラスを作ったら、上述のソースをコピペして、名前を変えてとりあえず保存。

JkvsServer.java

package net.yosiopp.jkvs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class JkvsServer {
  public static final int ECHO_PORT = 10007;

  public static void main(String args[]) {
    ServerSocket serverSocket = null;
    Socket socket = null;
    try {
      serverSocket = new ServerSocket(ECHO_PORT);
      System.out.println("EchoServerが起動しました(port="
          + serverSocket.getLocalPort() + ")");
      socket = serverSocket.accept();
      System.out.println("接続されました " + socket.getRemoteSocketAddress());
      BufferedReader in = new BufferedReader(new InputStreamReader(socket
          .getInputStream()));
      PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
      String line;
      while ((line = in.readLine()) != null) {
        System.out.println("受信: " + line);
        out.println(line);
        System.out.println("送信: " + line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (socket != null) {
          socket.close();
        }
      } catch (IOException e) {
      }
      try {
        if (serverSocket != null) {
          serverSocket.close();
        }
      } catch (IOException e) {
      }
    }
  }
}

まだこの状態だと本当にパクっただけです。

ここにKVSの要である、Mapを足します。
また、上記のコードままではクライアントと一回通信したら終わっちゃうので、
クライアントとの通信処理はThreadで処理するようにし、サーバが終わらないように修正します。

package net.yosiopp.jkvs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class JkvsServer {
  static final int DEFAULT_PORT = 59128;
  static final int INITIAL_CAPACITY = 100;
  static ConcurrentHashMap<String, byte[]> map = null;
  static ExecutorService svc = null;
  static boolean alive = true;

  public static void main(String args[]) {
    ServerSocket serverSocket = null;
    int port = -1;
    try {
      map = new ConcurrentHashMap<String, byte[]>(INITIAL_CAPACITY);
      svc = Executors.newCachedThreadPool();
      serverSocket = new ServerSocket(DEFAULT_PORT);
      port = serverSocket.getLocalPort();
      System.out.println("jkvs(" + port + ") start");
      while(alive){
        Socket socket = serverSocket.accept();
        svc.execute(new Task(socket));
      }
      svc.shutdownNow();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (serverSocket != null) {
          serverSocket.close();
        }
      } catch (IOException e) {
      }
    }
    System.out.println("jkvs(" + port + ") shutdown");
  }

  static class Task implements Runnable{
    Socket socket;

    public Task(Socket socket) {
      this.socket = socket;
    }

    @Override
    public void run(){
      try{
        // 入力
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Object request = (Object) ois.readObject();

        // 出力
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(request);
        oos.flush();

        oos.close();
        ois.close();
      }catch (Exception e) {
        e.printStackTrace();
      }finally{
        if(socket != null && !socket.isClosed()){
          try {
            socket.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }
}

大体こんな感じでしょうか。

とりあえずリクエストをそのままレスポンスに載せてます。

まとめ

長くなっちゃったので、まとめ(というかポイント説明)して今回はおわりにしたいと思います。

次回はクライアント側の作成とインターフェース部分やります。