やっとかく。
とりあえず、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ブロックである。
これでちゃんと排他制御をかけないと、
エラーになるぜ。。
ながいので、ラウンド編は次回に。
コメントがあればどうぞ