私ってば、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.yosiopp.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();
}
}
}
}
}
}
大体こんな感じでしょうか。
とりあえずリクエストをそのままレスポンスに載せてます。
まとめ
長くなっちゃったので、まとめ(というかポイント説明)して今回はおわりにしたいと思います。
- 使用するポート番号を変えました。59128。値は適当です。
- 値を保存するMapにはConcurrentHashMapを使います。
- 通信を処理するThreadにはExecutorServiceを使います。(とりあえずnewCachedThreadPoolにしてますが、性能面で問題があれば変えるかも。)
次回はクライアント側の作成とインターフェース部分やります。