async.js を使ってみる
callback 地獄に陥りやすい javascript さんを救うべくして現れた async.js 。
機能の解説や使い方は他の方が詳しくやっていると思うので、
- caolan/async · GitHub 公式とか
- node.jsのいろいろなモジュール17 – asyncで非同期処理のフロー制御 | Developers.IO Developers.IOとか
- JavaScript - async.jsでwaterfallとseries、parallelの違い - Qiita Qiitaとか
あたりをご覧いただければと。
で、今回は自分で使ってて「ん?」と思った部分を紹介。
続きを読むpaizaのオンラインハッカソンに参加してみた 第二弾
前回Pythonで挑戦しましたが、今回は最近で一番使用頻度の高いPHPで挑戦してみました。
コードはここに書くと縦長になるので、Ideoneでどうぞ。
Ideone.com - 09GG1A - Online PHP Interpreter & Debugging Tool
基本的にこのハッカソンはいかに効率良く必要な情報を集めるか(今回でいうとsearch関数の部分)のアルゴリズムを評価しているので、全体としてのコードはシンプルです。
akai_inuさんの採点結果[100点] す、凄いなんて思ってるわけじゃないんだから!|paizaオンラインハッカソンVol.2
結果はご覧の通り。やったぜ。
(まだ速度的には微妙だけども、私アルゴリズムには全く詳しくないので、これでいいや。)
追記 : 2014/04/18 18:23
Ideone.com - xTkoCI - Online PHP Interpreter & Debugging Tool
akai_inuさんの採点結果[100点] す、凄いなんて思ってるわけじゃないんだから!|paizaオンラインハッカソンVol.2
そういえば出力で毎回echoするのは遅いなーと思ってstringに溜めてから最後にechoするようにしてみましたがほとんど変わらず(最後が遅くなったのは多分実行側の状態の問題)。
paizaのオンラインハッカソンに参加してみた
お久しぶりです。最近またコーディング時間が増えてきたので、ちょくちょく記事書こうかなと思います。
ニコマガの方が実はメインだったのですが、ちょっとグーグラビリティが低いかなと思ったので、技術系記事はこっちに書こうと。
以前もやっていたオンラインハッカソン
新人女子プログラマの書いたコードを直すだけの簡単なお仕事です!|paizaオンラインハッカソンVol.1
には私も参加したのですが(結果は散々)、先日その第二弾が開始されたらしいので、また参加してみました。
Pythonで。
ほぼ初体験の言語です。
将来的にはデータ分析のお仕事が出来ればなと思っていて、分析に有用だといわれている言語が汎用言語(CとかJavaとかPHPとか)の中ではこのPythonさんが一番という話を聞いて、勉強がてらいじってみました。
で、そのハッカソンのページは↓
女子大生とペアプロするだけの簡単なお仕事です!|paizaオンラインハッカソンVol.2
元々paizaというページは、Web上で簡単なバッチ処理の課題が与えられ、それに対応したソースコードを提出して採点してもらい、自分のコーディング能力を定量化、そして企業にオファーをもらう、という就職支援サイト的な意味合いがあるのですが、そこでの宣伝イベントですね。
ハッカソンってのは、短期間でプログラマーが共同していろいろ競ったりする「ハック+マラソン」の造語。
内容に関しては該当ページを見てもらえれば良いと思いますが、ここでは私の書いたコードを紹介。
TCP通信を確立して定期的に送受信を行うライブラリ作った
ゲームのリアルタイム通信用のTCPライブラリです。
半分自分用に仕様を書き残しておきます。
全体仕様
主に DataProtocolクラス, MyTcpClientクラス, MyTcpListenerクラス の三つで構成。
DataProtocolクラス
実際の通信データであるバイト配列をDataProtocolクラスによってラップし、通信用ヘッダーと基本型のコンテンツ(変換機能付)を付加して送受信可能にするクラス。
最初の数バイト(DataProtocol.HEADERSIZE)は通信用のヘッダーとして裏側で送受信されるデータ。
そのあとはList
- byte Size - コンテンツのバイト数。
- byte ContentId - 利用側が確認する、コンテンツ識別番号。
- byte[] ContentBytes - 実際のコンテンツのバイト配列。
ContentData.GetContent(ContentDataType type) メソッドで実際のコンテンツのobjectを取得。
// 取得例 foreach (var content in data.ContentDatum) Console.Write((string)content.GetContent(ContentDataType.String));
これを使うことで使う側はバイト配列やIDの管理をしなくても通信データが確認できます。
MyTcpClientクラス
TcpClientクラスのラッパー。
これはサーバ側・クライアント側双方で使います。
通信接続・切断、DataProtocolデータ受信・送信を担当。
MyTcpListenerクラス
TcpListenerクラスのラッパー。
スレッドを生成して裏でデータの受信と新規接続の確認を行ってくれる。
SendData(byte remoteId, DataProtocol dp)メソッドを呼び出してデータを送信する
ここからは実際に使う時の流れ。
クライアント側
- MyTcpClient.Connect(string hostName, int port)メソッドで接続
- MyTcpClient.Availableがtrueの間バックグラウンドのスレッドで送受信ループ
- 送信:DataProtocolクラスを作成し初期化⇒MyTcpClient.SendData(DataProtocol dp)に渡して送信
- 受信:MyTcpClient.ReceiveData()メソッドでDataProtocolを受信してデータを処理
- 送受信時にエラーが出た際は自動で通信を終了し終了&明示的にMyTcpClient.Close()メソッドで終了可能
サーバ側
- MyTcpListenerを初期化してStart()
- 定期送信:MyTcpListener.SendingDataイベントで送信したいデータを別スレッドで処理
- 定期受信:MyTcpListener.ReceivingDataイベントで受信したデータを別スレッドで処理
- 新規接続:MyTcpListener.AddingClientイベントで新しい接続に関する処理
- 明示的に送信:MyTcpListener.SendData(DataProtocol dp)メソッドでデータ送信
- 送受信エラーかMyTcpListener.Stop()メソッドで終了
凄く簡単。
利用側は受信したDataProcotolのContentDataを、ContentIdを見ながら処理して送受信動作を行うだけです。
ゲームでの通信は大抵リアルタイム(秒間数~数十回)で送信と受信が繰り返されるので、メインスレッドとは別のスレッドを作ってそちらで送受信んを行うと思います。
サーバは別スレッドでのイベントがすでに用意されているので、そちらで定期的に送受信が可能。
クライアントもMyTcpClient.Connectに成功した際にスレッドが動き出しますので、イベントによる定期送受信ができます。
今はまだコンソール内でデバッグしているだけでまだゲームに適用していないので機能が足りないかもしれませんが、うまく動いてくれそうです。
今度は簡単な3D空間の移動と弾の処理、HP管理あたりを最低限実装したゲームにこのライブラリを導入して試してみます。
P.S. これだけ処理しても1,000行いかない小さいライブラリ
【今から二時間でLinux版マインクラフトサーバのシェルスクリプト叩くWebアプリ作る】 #cafecapy
発端(https://twitter.com/akai_inu/status/264597216218456064)
二時間制作シリーズ(?)第一回はマインクラフトのサーバ(jar/Linux上)をWeb上で制御出来るアプリを作る、です。
ひとまず最初に完成図↓
機能:マインクラフトサーバの状態確認・起動・終了・再起動・強制終了
※この記事では、"Linuxコマンド", "シェルスクリプト", "PHP", "javascript(jQuery)"を利用しています。
※CentOSによるサーバです。
※シェルスクリプトについてはバグが残っている可能性があります。
1.マインクラフトのサーバを用意する
$ mkdir /var/www/minecraftsv/ #どこでもいいさ $ cd /var/www/minecraftsv/ $ wget https://s3.amazonaws.com/MinecraftDownload/launcher/minecraft_server.jar $ java -Xmx128M -Xms1024M -jar minecraft_server.jar nogui # 念のため一度起動して関連ファイルを生成しておく
2.サーバのバックグラウンド動作用 screen を導入する
マインクラフトのサーバはjavaで動いているので、バックグラウンドで動作させるには一工夫必要である。
$ sudo apt-get install screen
特に設定することはないのでとりあえずインストールだけする。
3.操作用シェルスクリプトを作る
$ vim mcsv.sh 1 #!/bin/sh 2 RAM_MIN=64 # Minimum use of Memory 3 RAM_MAX=512 # Maximum use of Memory 4 SCREEN_NAME=minecraft_server_web 5 cd "${0%/*}" 6 7 check_server() 8 { 9 a=`screen -ls | grep "${SCREEN_NAME}"` 10 if [ ${#a} -gt 0 ] 11 then 12 echo "0" 13 else 14 echo "1" 15 fi 16 } 17 IS_SERVER_RUNNING=`check_server` 18 19 # Starting Server Function 20 start_server() 21 { 22 echo "Starting server..." 23 screen -AmdS ${SCREEN_NAME} java -Xms${RAM_MIN}M -Xmx${RAM_MAX}M -jar minecraft_server.jar nogui 24 echo "Server started." 25 } 26 27 # Stopping Server Function 28 stop_server() 29 { 30 echo "Stopping server..." 31 i=15 32 33 while [ ${i} -ne 0 ] 34 do 35 if test `expr ${i} % 15` -eq 0 -o ${i} -le 10 36 then 37 screen -S ${SCREEN_NAME} -p 0 -X eval "stuff say\040Server\040will\040stop\040in\040${i}\040seconds.\015" 38 echo "Server will stop in ${i} seconds." 39 fi 40 i=`expr ${i} - 1` 41 sleep 1 42 done 43 screen -S ${SCREEN_NAME} -p 0 -X eval 'stuff stop\015' 44 45 echo "Wait Saving..." 46 sleep 10 47 echo "Server stopped." 48 } 49 50 case $1 in 51 start) 52 if [ ${IS_SERVER_RUNNING} -eq 0 ] 53 then 54 echo "Maybe server is running. Exit shell." 55 else 56 start_server 57 fi 58 ;; 59 restart) 60 if [ ${IS_SERVER_RUNNING} -eq 0 ] 61 then 62 echo "Restarting server..." 63 stop_server 64 start_server 65 else 66 echo "Maybe server is not running. Exit shell." 67 fi 68 ;; 69 stop) 70 if [ ${IS_SERVER_RUNNING} -eq 0 ] 71 then 72 stop_server 73 else 74 echo "Maybe server is not running. Exit shell." 75 fi 76 ;; 77 terminate) 78 echo "Terminating Server..." 79 screen -S ${SCREEN_NAME} -X quit 80 echo "Terminated server." 81 ;; 82 status) 83 if [ ${IS_SERVER_RUNNING} -eq 0 ] 84 then 85 echo "Minecraft server is running." 86 else 87 echo "Minecraft server is not running." 88 fi 89 ;; 90 *) 91 echo "usage... $0 (start|restart|stop|status)" 92 ;; 93 esac 94
なんてことはないシェルスクリプト。ただしこれしきに試行錯誤して4時間くらいかかった。
# chmod a+x mcsv.sh # chown apache:apache mcsv.sh
実行権限付加+apacheユーザとして実行するので権限移譲
4.Web側を作る
まずアクセスするフォアグラウンドのhtmlファイル
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title>Minecraft Web Scriptor</title> 6 <script src="/jquery-1.8.2.min.js" type="text/javascript"></script> 7 8 <script type="text/javascript"> 9 function setButtonAndStatus(res) { 10 var arr = res.split("\n"); 11 // 最後は空行なので-2 12 var s = arr[arr.length - 2].search('not') == -1; 13 if(s) { 14 $("#button_start").attr('disabled', 'true'); 15 $("#button_stop").removeAttr('disabled'); 16 $("#button_restart").removeAttr('disabled'); 17 $("#status").text("サーバは起動しています"); 18 } else { 19 $("#button_start").removeAttr('disabled'); 20 $("#button_stop").attr('disabled', 'true'); 21 $("#button_restart").attr('disabled', 'true'); 22 $("#status").text("サーバは起動していません"); 23 } 24 } 25 function GetServerStatus() { 26 $.post("./exec.php", { command: "status" }, function(res) { 27 $("#statustime").text(new Date($.now()).toLocaleString()); 28 setButtonAndStatus(res); 29 }, "text"); 30 31 window.setTimeout("GetServerStatus()", 5000); 32 } 33 $(function(){ 34 // Start to get ServerStatus 35 GetServerStatus(); 36 37 var ajaxWaiting = false; 38 $(window).bind("beforeunload", function() { 39 console.log(ajaxWaiting); 40 if(ajaxWaiting) 41 return "現在サーバの操作中です。ページから離れる場合サーバエラーが発生する場合があります。"; 42 }); 43 44 $("#button_start").click(function(e) { 45 $("#button_start").attr('disabled', 'true'); 46 $("#warning").text("現在サーバを起動しています..."); 47 ajaxWaiting = true; 48 $.post("exec.php", { command: "start" }, function(res) { 49 $("#response").append(res); 50 setButtonAndStatus(res); 51 $("#warning").text(""); 52 ajaxWaiting = false; 53 }); 54 }); 55 $("#button_stop").click(function(e) { 56 $("#button_stop").attr('disabled', 'true'); 57 $("#button_restart").attr('disabled', 'true'); 58 $("#warning").text("現在サーバを終了しています..."); 59 ajaxWaiting = true; 60 $.post("exec.php", { command: "stop" }, function(res) { 61 $("#response").append(res); 62 setButtonAndStatus(res); 63 $("#warning").text(""); 64 ajaxWaiting = false; 65 }); 66 }); 67 $("#button_restart").click(function(e) { 68 $("#button_stop").attr('disabled', 'true'); 69 $("#button_restart").attr('disabled', 'true'); 70 $("#warning").text("現在サーバを終了しています..."); 71 ajaxWaiting = true; 72 $.post("exec.php", { command: "stop" }, function(res) { 73 $("#response").append(res); 74 $("#warning").text("現在サーバを起動しています..."); 75 $.post("exec.php", { command: "start" }, function(res) { 76 $("#response").append(res); 77 setButtonAndStatus(res); 78 $("#warning").text(""); 79 ajaxWaiting = false; 80 }); 81 }); 82 }); 83 $("#button_terminate").click(function(e) { 84 $.post("exec.php", { command: "terminate" }, function(res) { 85 $("#response").append(res); 86 setButtonAndStatus(res); 87 }); 88 }); 89 }); 90 </script> 91 </head> 92 <body> 93 94 <h1>Minecraft Web Scriptor</h1> 95 96 <div style="width:500px;margin:15px;color:#FFF;background:#000;padding:5px;border:3px ridge #FFF;">サーバ状態 : 97 <span id="status"></span>(@<span id="statustime"></span>) 98 <pre id="warning" style="font-weight:bold;text-align:center;font-size:150%;color:#FF5555;"></pre></div> 99 <input type="button" id="button_start" value="サーバを起動する" disabled="true" /> 100 <input type="button" id="button_stop" value="サーバを終了する" disabled="true" /> 101 <input type="button" id="button_restart" value="サーバを再起動する" disabled="true" /> 102 <input type="button" id="button_terminate" value="サーバを強制終了する" /> 103 <br /><br /> 104 <div style="width:500px;border:3px ridge #FFF;margin:15px;padding:5px;color:#FFF;background:#000;"> 105 サーバ応答 : 106 <pre id="response"></pre> 107 </div> 108 </body> 109 </html>
最低限しか書いていないので各自補完すべし。
ここでの大事な所は $.post 関数。
jQueryでajaxを簡単に行うためのもので、これはphpやcgiにPOSTをパラメータ付きで投げて成功時にcallbackを実行するもの。
失敗時にエラー処理を行うには$.ajax関数を使わなくてはいけなくて面倒なので今回は省略。実際運用する時はつけないといけないかもしれぬ。
1 <?php 2 $filepath = '/var/www/minecraftsv/mcsv.sh '; 3 exec($filepath.$_POST["command"], $arr); 4 foreach($arr as $a) { 5 echo "$a\n"; 6 } 7 8 // status以外は追加でstatusを取得 9 if($_POST["command"] != "status") { 10 exec($filepath."status", $arrstat); 11 foreach($arrstat as $a) { 12 echo "$a\n"; 13 } 14 } 15 ?>
キモはPHPの exec コマンド。
apacheユーザとしてサーバ内のシェルスクリプトを実行することが出来る。
第二引数で実行時にechoされた内容を行ごとに配列で取得。
後は.htaccessでBasic認証をかける。かけないと誰でもサーバ再起動し放題で #死
ということで、第一弾は2時間どころか7時間もかかってしまった。試行錯誤の時間を縮めたい所である。
記事にするのが面倒になってかなり説明をはしょっている。よって利用する際に何かわからないことがあればコメント欄まで出してほしい。