ラウンド編。

また、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は書く気力なし。。

コメントがあればどうぞ


CAPTCHA Image
Reload Image