Socketプログラミング(1)では、サーバ側の実装ととりあえず行いました。
今回は、クライアント側の実装と、インターフェース部分を実装していきたいと思います。
インターフェースを決める
サーバとクライアントが通信をする上で、どんなメッセージをやり取りすれば相手に伝わるのか。
とりあえずはインターフェースを決めましょう。
性能優先してガッチガチの仕様を決めても良いんですが、
面倒くさいので今回は通信用オブジェクトをシリアライズして投げつける方向でいきます。
JkvsRequest.java
package net.yosiopp.jkvs;
import java.io.Serializable;
public class JkvsRequest implements Serializable{
private static final long serialVersionUID = 1L;
public static enum METHOD{
GET, PUT, DELETE, LIST, COUNT,
KILL
}
public JkvsRequest(METHOD method){
this.method = method;
}
private METHOD method;
private String key;
private byte[] value;
public void setMethod(METHOD method) {
this.method = method;
}
public METHOD getMethod() {
return method;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setValue(byte[] value) {
this.value = value;
}
public byte[] getValue() {
return value;
}
}
JkvsResponse.java
package net.yosiopp.jkvs;
import java.io.Serializable;
public class JkvsResponse implements Serializable{
private static final long serialVersionUID = 1L;
private int status;
private byte[] value;
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
public void setValue(byte[] value) {
this.value = value;
}
public byte[] getValue() {
return value;
}
}
こんなカンジで良いでしょう。
サーバに対してJkvsRequestを投げつけると、サーバはJkvsRequestを元に処理し、結果をJkvsResponseに詰めてクライアントに返します。
そんなイメージです。
クライアント側の実装
クライアント用に新しいプロジェクトを作成します。
前回は jkvss でしたよね。今回は jkvs とします。
パッケージは前回と同じで、net.yosiopp.jkvs とします。
まず、上記のJkvsRequestクラスとJkvsResponseクラスを同パッケージに配置します。
そして、Jkvsクラスを作成します。
Jkvs.java
package net.yosiopp.jkvs;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.List;
public class Jkvs {
static final int DEFAULT_PORT = 59128;
private final String host;
private final int port;
public Jkvs(){
this("localhost", DEFAULT_PORT);
}
public Jkvs(String host){
this(host, DEFAULT_PORT);
}
public Jkvs(String host, int port){
this.host = host;
this.port = port;
}
/**
* Jkvsサーバとの通信処理
* @param request リクエスト情報
* @return レスポンス情報
*/
private JkvsResponse communicate(JkvsRequest request){
JkvsResponse res = null;
Socket socket = null;
try {
socket = new Socket(host, port);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(request);
oos.flush();
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
ObjectInputStream ois = new ObjectInputStream(bis);
res = (JkvsResponse) ois.readObject();
ois.close();
oos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
}
}
return res;
}
@SuppressWarnings("unchecked")
public <T> T get(String key){
T value = null;
JkvsRequest req = new JkvsRequest(JkvsRequest.METHOD.GET);
req.setKey(key);
JkvsResponse res = communicate(req);
if(res != null){
try {
value = (T)b2o(res.getValue());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return value;
}
public void put(String key, Object value){
try {
JkvsRequest req = new JkvsRequest(JkvsRequest.METHOD.PUT);
req.setKey(key);
req.setValue(o2b(value));
communicate(req);
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
public <T> T delete(String key){
T value = null;
JkvsRequest req = new JkvsRequest(JkvsRequest.METHOD.DELETE);
req.setKey(key);
JkvsResponse res = communicate(req);
if(res != null){
try {
value = (T)b2o(res.getValue());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return value;
}
@SuppressWarnings("unchecked")
public List<String> list(){
List<String> value = null;
JkvsRequest req = new JkvsRequest(JkvsRequest.METHOD.LIST);
JkvsResponse res = communicate(req);
if(res != null){
try {
value = (List<String>)b2o(res.getValue());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return value;
}
public int count(){
Integer value = null;
JkvsRequest req = new JkvsRequest(JkvsRequest.METHOD.COUNT);
JkvsResponse res = communicate(req);
if(res != null){
try {
value = (Integer)b2o(res.getValue());
return value.intValue();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return 0;
}
final static byte[] o2b(Object object) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(object);
byte[] bytes = bos.toByteArray();
out.close();
bos.close();
return bytes;
}
final static Object b2o(byte[] bytes) throws
IOException, ClassNotFoundException {
return new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
}
}
クライアント側は一気に書いてしまいましたが、
このJkvsクラスを使ってJkvsサーバと通信するイメージです。
getは値取得、putは値設定、deleteは値削除。
listはキーリストの取得。countはキー数の取得。
また、インターフェースのMETHOD列挙に用意してあるKILLはサーバの停止コマンドの想定です。
(が、クライアントから容易にサーバを停止できるのは設計上微妙な気がするのでとりあえず実装してません)
その他ポイント
- communicateメソッドがサーバとの通信部分となります
- コンストラクタでホストおよびポートを変更可能にしてあります
- JkvsRequestのvalueはbyte[]型なので、o2bメソッドでObject型をbyte[]型に変換しています
- 逆に、JkvsResponseのvalueをにはb2oメソッドでObject型に戻してあげます
- get,deleteは総称型で値を返しますので、cast時にwarningが出ないようになってます
次回はサーバ側の修正です。