やっとかく。

とりあえず、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ブロックである。

これでちゃんと排他制御をかけないと、

エラーになるぜ。。

 

ながいので、ラウンド編は次回に。

 

コメントがあればどうぞ


CAPTCHA Image
Reload Image