ラウンド編。
また、websocketの処理だけ書く。
package trmpj.ws; 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.TPair; import trmpj.TReq; import trmpj.TSession; import trmpj.TWs; import trmpj.req.TReqEnd; import trmpj.req.TReqGame; import trmpj.req.TReqPrepared; import trmpj.res.TResEnded; import trmpj.res.TResError; import trmpj.res.TResGame; import trmpj.res.TResInvalid; import trmpj.res.TResNone; import trmpj.res.TResStart; import trmpj.res.TResWait; /** * 対戦用処理 * */ @ServerEndpoint(value = "/ws/round/{pairId}") public class TWsRound extends TWs { /** * pairId => TPairのマップ */ private static ConcurrentHashMap<String, TPair> pairs = new ConcurrentHashMap<String, TPair>(); /** * pairId => Threadのマップ */ private static ConcurrentHashMap<String, Thread> threads = new ConcurrentHashMap<String, Thread>(); /** * openハンドラ * @param pairId * @param session */ @OnOpen public void onOpen(@PathParam("pairId") String pairId, 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 pairId * @param session * @param message */ @OnMessage public void onMessage(@PathParam("pairId") String pairId, 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(); } // ペアIDに応じたペア情報をマップから取り出す if (!pairs.containsKey(pairId)) { pairs.putIfAbsent(pairId, new TPair(pairId)); } TPair pair = pairs.get(pairId); // ペア情報に排他制御をかける // ※重要 synchronized (pair) { if (req.command.equals(TReq.COMMAND_PREPARED)) { // preparedコマンド TReqPrepared reqPrepared = ts.receiveMessage(message, TReqPrepared.class); if (reqPrepared == null) { throw new Exception(); } // 既にペアが揃った状態ならここで終了 if (pair.isPrepared()) { throw new Exception(); } // 自分の情報をセッションに追加 ts.setUser(reqPrepared.user); // ペア情報に自分の情報を追加 pair.addTSession(ts); // 同一ユーザIDはエラー if (pair.isDuplicated()) { throw new Exception(); } // 2人そろった状態なら、2人にstartを返す if (pair.isPrepared()) { // スレッドを止める threads.remove(pairId); TResStart resStart = new TResStart(); resStart.users.addAll(pair.getUsers()); pair.sendMessage(resStart); return; } // 揃っていない状態なので、自分にwaitを返す TResWait resWait = new TResWait(); ts.sendMessage(resWait); // 別スレッドで、相手がくるまで数秒待機する if (!threads.containsKey(pairId)) { threads.putIfAbsent(pairId, new Thread(new TRunnable(pairId))); threads.get(pair).start(); } return; } else if (req.command.equals(TReq.COMMAND_GAME)) { // gameコマンド TReqGame reqGame = ts.receiveMessage(message, TReqGame.class); if (reqGame == null) { throw new Exception(); } // 同一ユーザIDはエラー if (pair.isDuplicated()) { throw new Exception(); } // 揃った状態でない、または終了状態なら何もしない if (!pair.isPrepared() || pair.isEnded()) { throw new Exception(); } // 2人に返す TResGame resGame = new TResGame(); resGame.useId = ts.getUser().userId; resGame.data = reqGame.data; pair.sendMessage(resGame); return; } else if (req.command.equals(TReq.COMMAND_END)) { // endコマンド TReqEnd reqEnd = ts.receiveMessage(message, TReqEnd.class); if (reqEnd == null) { throw new Exception(); } // 同一ユーザIDはエラー if (pair.isDuplicated()) { throw new Exception(); } // ペアを終了状態にする pair.setEnded(true); // 自分に返す TResEnded resEnded = new TResEnded(); ts.sendMessage(resEnded); return; } } } catch (Exception e) { e.printStackTrace(); } // エラー処理 if (ts != null) { TResError resError = new TResError(); ts.sendMessage(resError); } } /** * closeハンドラ * @param pairId * @param session * @param reason */ @OnClose public void onClose(@PathParam("pairId") String pairId, 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); // ペアIDに応じたペア情報をマップから取り出す if (!pairs.containsKey(pairId)) { throw new Exception(); } TPair pair = pairs.get(pairId); // ペア情報に排他制御をかける // ※重要 synchronized (pair) { // 自分の情報削除 pair.removeTSession(ts); // 空になっていたら削除 if (pair.isEmpty()) { pairs.remove(pairId); return; } // game中で、endでない場合は、残っている方にnoneを送信 if (pair.isPrepared() && !pair.isEnded()) { TResNone resNone = new TResNone(); pair.sendMessage(resNone); return; } } } catch (Exception e) { e.printStackTrace(); } // セッション情報も破棄する sessions.remove(accessdId); } /** * 2人が揃うまで待つ別スレッド * @author furuta * */ public class TRunnable implements Runnable { /** * ペアID */ private String pairId; /** * コンストラクタ * @param pairId */ public TRunnable(String pairId) { this.pairId = pairId; } /** * 処理 */ public void run() { try { // 10秒まつ Thread.sleep(10000); // ペアを取得 if (!pairs.containsKey(pairId)) { throw new Exception(); } TPair pair = pairs.get(pairId); synchronized (pair) { if (!pair.isPrepared()) { // 揃っていない状態であれば、残っている人にinvalidを返す TResInvalid resInvalid = new TResInvalid(); pair.sendMessage(resInvalid); } } } catch (Exception e) { e.printStackTrace(); } // スレッドを削除 threads.remove(pairId); } } }
ポイントはsynchronizedでしっかり排他制御をかけること。
これをやらないとだめだぜ。。
ソースコードはちゃんとテストしてないけど、
処理の流れはわかるはず。。
とりあえず、作成したコードをzipで固めておきます。
これで、websocketは終了。
nodeは書く気力なし。。
コメントがあればどうぞ