カテゴリー「websocket」

前回ゲーム仕様を決めたので、

今回は詳細は処理フローを作成します。

以下のような感じを想定しています。

websocket_seq

上記イメージの解説です。

  1. マッチングサーバと接続します。
  2. エントリーに必要な情報をおくります。サーバ側が1人目の受付の場合、「受付完了」という情報を返します。もし、ここで、2人目なら、「成立完了」という情報を2人に返します。
  3. 「成立完了」を受け取ったクライアントは、マッチングサーバから抜けて、対戦サーバ側と接続します。
  4. 対戦サーバと接続したら、「準備完了」を送ります。ここで、2人がそろって初めて「開始」を返します。
  5. ゲーム中は、片側(相手)から送られた情報を、2人に送信します。
  6. 無事ゲームが終わったら(終了判断はクライアントに依存)、1人1人が別々に「終了」を送信し、「終了」を受け取ったら各人で、対戦サーバから抜けます。
  7. もし、ゲームが始まっている状態で、片側(相手)が切断したら、残っている方に「相手が切断した」という情報を送ります。今回の仕様では、相手の勝利となり、相手側は「終了」を送信します。

ポイントは4、6、7です。

4のとき、相手が来ないケースもあるので、制限時間以内に相手が来なかったらどのような情報を返すかをサーバ側で対応する必要があります。

6はなぜ2人に送信しないかというと、下手に2人に送信してしまうと、終了時のクライアントの表示を片側が変更可能となってしまうからです。

7は片方の切断時に、残っている方へ何かしらの通知を行う対応です。

 

pub/subを使わない前提である以上、

上記の操作をすべてメモリ上の変数にて制御をかけます。

(※当然サービスのレベルでは、何かしらのデータベースへ書き込む等がありますが、ここでは考慮しません。)

 

次は、サーバ側のクラス設計を書きます。

 

投稿日時:2014年12月01日 12:43   カテゴリー:java, node.js, websocket   [コメントがあればどうぞ]

今のところ、3回にわたりwebsocketの比較を書いてきましたが、

これからは、

  • 処理フローの設計
  • プログラミング

となります。

 

とその前に、一服。

 

個人的な見解としては、

node.jsよりjavaの方がwebsocketは組みやすいかなと思っています。

理由は、

  • 過去資産が豊富なので、選択肢が多い
  • 型制約があるので、全体的に堅牢になる
  • eclipseなどのIDEが優秀

といったところでしょうか。

node.jsはjavascriptなので、

なんでもできてしまう分(たとえば、ユーザ定義オブジェクトになんでも突っ込めるとか)、

コードがわかりにくくなりがちです。(※規約で気を付けないとね)

また、IOが基本的に非同期で実行されるとかが、

やっぱりやりにくいなと感じてます。

ただ、java側はAPサーバを動かすコスト(メモリ消費)がやや高いので、

省エネなのはnode.jsかなとも思います。

 

ちなみに、javaでwebsocketをやる場合、

jetty8とかは独自実装になっており、

jetty9でJSR356に対応してます。

※jetty9の場合、jettyの独自パッケージの中で、JSR356に読み替えているようです。mavenのpomファイルも後で作成します。

JSR356に対応しておけば、tomcatに移植しても動くので、独自実装はもう使わないほうがよかです。

なお、あのplayframework2.3のwebsocketはjetty8でした。(playは機能面はすごいけど、ちょっと重いよね。。)

 

最終的には、どちらもクセはあります。

取り巻く環境、開発者の経験等を考慮して、

選択するとよいと思います。

(go言語なんかでもやっている人はいるのかな?goはこれから勉強します。)

 

投稿日時:2014年11月26日 13:04   カテゴリー:java, node.js, websocket   [コメントがあればどうぞ]

前回の続き。

前回の大切な前提として、

pub/subはなし(つまり、メモリ内で処理を完結可能)

というのがありました。

 

実際に対戦ゲームの作成を通して、処理フローを整理しましょう。

別に面白いものを作る必要はないので、

簡単なゲームとして、以下のような仕様とします。

  1. 2人対戦の神経衰弱
  2. ゲームの種別として、4×4、4×8、4×12の3種類(使用枚数が16枚、32名、48枚)
  3. ユーザはマッチング時に、ゲーム種別を選択可能
  4. マッチングが成立したらゲーム開始
  5. ゲーム中に離脱した場合、その時点で残っている人の勝利
  6. 離脱者なしにゲームが終わった場合、自分の取得枚数が多ければ勝利で、少なければ敗北

 

pub/subはないので、

スケールアウトの手段が現状皆無です。

そこで、URLを絡めて、効果的にスケールアウトができるようにしておきましょう。

URLはこんな感じです。

【マッチング】 /ws/matching/${type}

${type}とは、上記の4×4などをコード化したもの

【対戦】 /ws/round/${pairId}

${pairId}とは対戦者同士を結びつける一意のキーで、形式は0-9a-zの繰り返し

 

こんな感じでURLを設計しておくことで、

websocketのリバースプロキシ可能なサーバ(nginxやapache2.4など)で、

URLに応じた振り分けが可能となります。

この設計方式だと、マッチングが成立したら、

一旦マッチングサーバとの接続を閉じて、

新たに対戦サーバと接続する必要があります。

 

※nginxの設定例

vim /etc/nginx/websocket_params

proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Document-URI $document_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

vim /etc/nginx/conf.d/game.conf


# マッチング4x4
upstream ws_matching_4x4 {
    server localhost:8081;
    server localhost:8082 backup;
}
# マッチング4x8
upstream ws_matching_4x8 {
    server localhost:8083;
    server localhost:8084 backup;
}
# マッチング4x12
upstream ws_matching_4x12 {
    server localhost:8085;
    server localhost:8086 backup;
}

# 対戦(ペアIDの先頭がcからnまでの12個)
upstream ws_round_0b {
    server localhost:8181;
    server localhost:8182 backup;
}
# 対戦(ペアIDの先頭がcからnまでの12個)
upstream ws_round_cn {
    server localhost:8183;
    server localhost:8184 backup;
}
# 対戦(ペアIDの先頭がoからzまでの12個)
upstream ws_round_oz {
    server localhost:8185;
    server localhost:8186 backup;
}

server {
    listen 80;
    server_name game.example.com;

    charset utf-8;
    root /var/www/game/public;

    # エラーページ
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    # websocket_paramsのインクルード
    include /etc/nginx/websocket_params;

    # マッチング4x4
    location ~ ^/ws/matching/4/$ {
        proxy_pass http://ws_matching_4x4;
    }
    # マッチング4x8
    location ~ ^/ws/matching/8/$ {
        proxy_pass http://ws_matching_4x8;
    }
    # マッチング4x12
    location ~ ^/ws/matching/12/$ {
        proxy_pass http://ws_matching_4x12;
    }

    # 対戦(ペアIDの先頭がcからnまでの12個)
    location ~ ^/ws/round/[0-9a-b][0-9a-z]+?/$ {
        proxy_pass http://ws_round_0b;
    }
    # 対戦(ペアIDの先頭がcからnまでの12個)
    location ~ ^/ws/round/[c-n][0-9a-z]+?/$ {
        proxy_pass http://ws_round_cn;
    }
    # 対戦(ペアIDの先頭がoからzまでの12個)
    location ~ ^/ws/round/[o-z][0-9a-z]+?/$ {
        proxy_pass http://ws_round_oz;
    }
}

※全部localhostにしてポートを変更していますが、細分化すれば、上記設定で12台に切り分けられます。

※もちろん、もっと細かく分けることも可能です。

 

という具合で考えると、

ちょっと大げさに以下のようなイメージでインフラが考えられます。

websocket_struct

 

 

このようにフロントのサーバの機能を利用することで、

pub/subを使わずにwebsocketを通じたメッセージのやり取りができます。

狙いとしては

同一属性者を同じブロセスに閉じ込めることで、メモリ内の操作で処理を完結させる

ということになります。

 

投稿日時:2014年11月25日 18:17   カテゴリー:java, node.js, websocket   [コメントがあればどうぞ]

さて、本腰入れて。

今回の比較に当たり、大きな前提として、

pub/subはなし(つまり、メモリ内で処理を完結可能)

というのをあげておきます。

というのも、pub/subがあるときと、ないときでは、

結構実装が異なってしまうためです。

(※うまく抽象化できればいいのですが、node.js側は上手に継承を使うのが難しいので)

 

この前提のもと、

javaとnode.jsの大きな違いは、

java:マルチスレッド

node.js:シングルスレッド

ということであります。

 

この違いは、プログラムを書く上で、

排他制御」が必要か、不要かということにつながります。

 

たとえば、対戦ゲームを作るとした場合、

ゲームには「マッチング」と「対戦中」で処理が大きく2つに分かれます。

マッチングは諸条件あるものの、基本的には来た順番にさばく手法がとられると思います。

このとき、javaでは必ず排他制御をかける必要があります。

それに対し、node.jsでは、シングルスレッドなので、排他制御をかける必要はありません。

 

ここが両者の最大の違いとなります。

 

※あくまでメモリ内で完結する場合です。(つまり1プロセス)

複数台を使う場合は、node.jsでも排他制御が必要となる場合があります。

投稿日時:2014年11月25日 14:23   カテゴリー:java, node.js, websocket   [コメントがあればどうぞ]

websocketというと、チマタではnode.jsが流行っていますね。

 

しかし、javaもJSR356という規約の下、

各サーブレットコンテナで対応が完了しており、

個人的にはnode.jsと対をなす位までの能力があるかと思います。

 

javaでもnode.jsでもwebsocketを構築した筆者が、

数回にわたりお互いの特徴と実装の注意点などを書いていきます。

 

なお、バージョンは

【java】

jetty9.X(JSR356に対応した形)

【node.js】

v10.3X

となります。

 

 

投稿日時:2014年11月25日 13:37   カテゴリー:java, node.js, websocket   [コメントがあればどうぞ]