やっとかく。
とりあえず、websocketのメイン処理をさらす。
マッチング
package trmpj.ws;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import trmpj.TReq;
import trmpj.TSession;
import trmpj.TWs;
import trmpj.req.TReqEntry;
import trmpj.res.TResEntried;
import trmpj.res.TResError;
import trmpj.res.TResPrepare;
/**
* マッチング用処理
*
*/
@ServerEndpoint(value = "/ws/matching/{type}")
public class TWsMatching extends TWs {
/**
* type => Set<TSession>のマップ
*/
private static ConcurrentHashMap<Integer, LinkedList<TSession>> entries = new ConcurrentHashMap<Integer, LinkedList<TSession>>();
/**
* openハンドラ
* @param type
* @param session
*/
@OnOpen
public void onOpen(@PathParam("type") int type, Session session) {
// セッションにアクセスIDを格納する
String accessdId = generateAccessId();
session.getUserProperties().put(KEY_ACCESS_ID, accessdId);
// アクセスIDを、onMessage, onCloseで取得するときの関連を付ける
// ※sessionsのキー側はsessionでもよいのですが、
// runnableからの参照時に、セッションが破棄されていると困るので、
// あえて文字列にしている
TSession ts = new TSession(session);
sessions.putIfAbsent(accessdId, ts);
}
/**
* messageハンドラ
* @param type
* @param session
* @param message
*/
@OnMessage
public void onMessage(@PathParam("type") int type, Session session, String message) {
// アクセスIDを取得
String accessdId = (String) session.getUserProperties().get(KEY_ACCESS_ID);
TSession ts = null;
try {
// アクセスIDをもとに、openハンドラで登録したTSessionを呼び出す
if (!sessions.containsKey(accessdId)) {
throw new Exception();
}
ts = sessions.get(accessdId);
// リクエストを解析
TReq req = ts.receiveMessage(message, TReq.class);
if (req == null) {
throw new Exception();
}
// タイプに応じたエントリー情報をマップから取り出す
if (!entries.containsKey(type)) {
entries.putIfAbsent(type, new LinkedList<TSession>());
}
LinkedList<TSession> es = entries.get(type);
// タイプごとのエントリー情報に排他制御をかける
// ※重要
synchronized (es) {
if (req.command.equals(TReq.COMMAND_ENTRY)) {
// entryコマンド
TReqEntry reqEntry = ts.receiveMessage(message, TReqEntry.class);
if (reqEntry == null) {
throw new Exception();
}
// 自分の情報をセッションに追加
ts.setUser(reqEntry.user);
try {
// 先にエントリーしている人の情報をとる
TSession tsPrev = es.pop();
// 情報をとった結果、同じユーザIDの場合は、エラーとする
if (tsPrev.getUser().userId.equals(ts.getUser().userId)) {
throw new Exception();
}
// エントリーしている人の情報と、自分の情報を、マッチング成立として両者に返す
TResPrepare resPrepare = new TResPrepare();
resPrepare.type = type;
resPrepare.seed = (int) (Math.random() * 10000) + 1;
resPrepare.users.add(ts.getUser());
resPrepare.users.add(tsPrev.getUser());
ts.sendMessage(resPrepare);
tsPrev.sendMessage(resPrepare);
return;
} catch (Exception e) {
// ここはpopで例外が発生するが、無視する
}
// 自分を加える
es.add(ts);
// 自分にエントリー完了を送信する
TResEntried resEntried = new TResEntried();
ts.sendMessage(resEntried);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// エラー処理
if (ts != null) {
TResError resError = new TResError();
ts.sendMessage(resError);
}
}
/**
* closeハンドラ
* @param type
* @param session
* @param reason
*/
@OnClose
public void onClose(@PathParam("type") int type, Session session, CloseReason reason) {
// アクセスIDを取得
String accessdId = (String) session.getUserProperties().get(KEY_ACCESS_ID);
TSession ts = null;
try {
// アクセスIDをもとに、openハンドラで登録したTSessionを呼び出す
if (!sessions.containsKey(accessdId)) {
throw new Exception();
}
ts = sessions.get(accessdId);
// 自分が既に登録されていたら、削除する
if (!entries.containsKey(type)) {
throw new Exception();
}
LinkedList<TSession> es = entries.get(type);
es.remove(ts);
} catch (Exception e) {
e.printStackTrace();
}
// セッション情報も破棄する
sessions.remove(accessdId);
}
}
最大のポイントは、
onMessageにおけるsynchronizedブロックである。
これでちゃんと排他制御をかけないと、
エラーになるぜ。。
ながいので、ラウンド編は次回に。
コメントがあればどうぞ