javaからのmail送信ですが、
いろいろ情報が混在しております。
ただ、現時点で、
pom.xmlに以下を記載すれば、
javaSEでも動きます。
<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.5.0-b01</version> </dependency>
以上
IT系のめもを蓄積していこうかと
javaからのmail送信ですが、
いろいろ情報が混在しております。
ただ、現時点で、
pom.xmlに以下を記載すれば、
javaSEでも動きます。
<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.5.0-b01</version> </dependency>
以上
jdk7からjdk8に変えた時、
Runnableのスコープ外で宣言した変数が、
Runnable内でfinalを付けないで参照できた。
java7だと、
int numberLocal = 1; Thread t = new Thread(new Runnable() { final int numberThread = numberLocal; };
java8だと、
int numberLocal = 1; Thread t = new Thread(new Runnable() { int numberThread = numberLocal; };
のような感じで、java8だとfinalが不要らしい。
ただし、書き換えるとエラーになるので、
暗黙的なfinalということになるようです。
前回ゲーム仕様を決めたので、
今回は詳細は処理フローを作成します。
以下のような感じを想定しています。
上記イメージの解説です。
ポイントは4、6、7です。
4のとき、相手が来ないケースもあるので、制限時間以内に相手が来なかったらどのような情報を返すかをサーバ側で対応する必要があります。
6はなぜ2人に送信しないかというと、下手に2人に送信してしまうと、終了時のクライアントの表示を片側が変更可能となってしまうからです。
7は片方の切断時に、残っている方へ何かしらの通知を行う対応です。
pub/subを使わない前提である以上、
上記の操作をすべてメモリ上の変数にて制御をかけます。
(※当然サービスのレベルでは、何かしらのデータベースへ書き込む等がありますが、ここでは考慮しません。)
次は、サーバ側のクラス設計を書きます。
今のところ、3回にわたりwebsocketの比較を書いてきましたが、
これからは、
となります。
とその前に、一服。
個人的な見解としては、
node.jsよりjavaの方がwebsocketは組みやすいかなと思っています。
理由は、
といったところでしょうか。
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はこれから勉強します。)
前回の続き。
前回の大切な前提として、
pub/subはなし(つまり、メモリ内で処理を完結可能)
というのがありました。
実際に対戦ゲームの作成を通して、処理フローを整理しましょう。
別に面白いものを作る必要はないので、
簡単なゲームとして、以下のような仕様とします。
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台に切り分けられます。
※もちろん、もっと細かく分けることも可能です。
という具合で考えると、
ちょっと大げさに以下のようなイメージでインフラが考えられます。
このようにフロントのサーバの機能を利用することで、
pub/subを使わずにwebsocketを通じたメッセージのやり取りができます。
狙いとしては
「同一属性者を同じブロセスに閉じ込めることで、メモリ内の操作で処理を完結させる」
ということになります。
さて、本腰入れて。
今回の比較に当たり、大きな前提として、
pub/subはなし(つまり、メモリ内で処理を完結可能)
というのをあげておきます。
というのも、pub/subがあるときと、ないときでは、
結構実装が異なってしまうためです。
(※うまく抽象化できればいいのですが、node.js側は上手に継承を使うのが難しいので)
この前提のもと、
javaとnode.jsの大きな違いは、
java:マルチスレッド
node.js:シングルスレッド
ということであります。
この違いは、プログラムを書く上で、
「排他制御」が必要か、不要かということにつながります。
たとえば、対戦ゲームを作るとした場合、
ゲームには「マッチング」と「対戦中」で処理が大きく2つに分かれます。
マッチングは諸条件あるものの、基本的には来た順番にさばく手法がとられると思います。
このとき、javaでは必ず排他制御をかける必要があります。
それに対し、node.jsでは、シングルスレッドなので、排他制御をかける必要はありません。
ここが両者の最大の違いとなります。
※あくまでメモリ内で完結する場合です。(つまり1プロセス)
複数台を使う場合は、node.jsでも排他制御が必要となる場合があります。
websocketというと、チマタではnode.jsが流行っていますね。
しかし、javaもJSR356という規約の下、
各サーブレットコンテナで対応が完了しており、
個人的にはnode.jsと対をなす位までの能力があるかと思います。
javaでもnode.jsでもwebsocketを構築した筆者が、
数回にわたりお互いの特徴と実装の注意点などを書いていきます。
なお、バージョンは
【java】
jetty9.X(JSR356に対応した形)
【node.js】
v10.3X
となります。