Cafe capybara TECH-BLOG

PHPな会社でゴリゴリしてるあかいいぬの技術ブログです

async.js を使ってみる

callback 地獄に陥りやすい javascript さんを救うべくして現れた async.js 。
機能の解説や使い方は他の方が詳しくやっていると思うので、

あたりをご覧いただければと。


で、今回は自分で使ってて「ん?」と思った部分を紹介。

続きを読む

paizaのオンラインハッカソンに参加してみた 第二弾

前回Pythonで挑戦しましたが、今回は最近で一番使用頻度の高いPHPで挑戦してみました。

コードはここに書くと縦長になるので、Ideoneでどうぞ。
Ideone.com - 09GG1A - Online PHP Interpreter & Debugging Tool

基本的にこのハッカソンはいかに効率良く必要な情報を集めるか(今回でいうとsearch関数の部分)のアルゴリズムを評価しているので、全体としてのコードはシンプルです。


akai_inuさんの採点結果[100点] す、凄いなんて思ってるわけじゃないんだから!|paizaオンラインハッカソンVol.2

f:id:expert88:20140418150417p:plain
結果はご覧の通り。やったぜ。

(まだ速度的には微妙だけども、私アルゴリズムには全く詳しくないので、これでいいや。)

追記 : 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)メソッドを呼び出してデータを送信する



ここからは実際に使う時の流れ。

クライアント側

  1. MyTcpClient.Connect(string hostName, int port)メソッドで接続
  2. MyTcpClient.Availableがtrueの間バックグラウンドのスレッドで送受信ループ
    1. 送信:DataProtocolクラスを作成し初期化⇒MyTcpClient.SendData(DataProtocol dp)に渡して送信
    2. 受信:MyTcpClient.ReceiveData()メソッドでDataProtocolを受信してデータを処理
  3. 送受信時にエラーが出た際は自動で通信を終了し終了&明示的にMyTcpClient.Close()メソッドで終了可能

サーバ側

  1. MyTcpListenerを初期化してStart()
  2. 定期送信:MyTcpListener.SendingDataイベントで送信したいデータを別スレッドで処理
  3. 定期受信:MyTcpListener.ReceivingDataイベントで受信したデータを別スレッドで処理
  4. 新規接続:MyTcpListener.AddingClientイベントで新しい接続に関する処理
  5. 明示的に送信:MyTcpListener.SendData(DataProtocol dp)メソッドでデータ送信
  6. 送受信エラーか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上で制御出来るアプリを作る、です。

ひとまず最初に完成図↓
f:id:expert88:20121103211919p:plain

機能:マインクラフトサーバの状態確認・起動・終了・再起動・強制終了

※この記事では、"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

特に設定することはないのでとりあえずインストールだけする。

(参考: てりログ minecraftサーバの導入と設定

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ユーザとして実行するので権限移譲

(参考: てりログ minecraftサーバの管理(自動リスタート)

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 関数。
jQueryajaxを簡単に行うためのもので、これはphpやcgiにPOSTをパラメータ付きで投げて成功時にcallbackを実行するもの。
失敗時にエラー処理を行うには$.ajax関数を使わなくてはいけなくて面倒なので今回は省略。実際運用する時はつけないといけないかもしれぬ。

次に、シェルスクリプト実行用のphpファイル

     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  ?>

キモはPHPexec コマンド。
apacheユーザとしてサーバ内のシェルスクリプトを実行することが出来る。
第二引数で実行時にechoされた内容を行ごとに配列で取得。

後は.htaccessBasic認証をかける。かけないと誰でもサーバ再起動し放題で #死


ということで、第一弾は2時間どころか7時間もかかってしまった。試行錯誤の時間を縮めたい所である。

記事にするのが面倒になってかなり説明をはしょっている。よって利用する際に何かわからないことがあればコメント欄まで出してほしい。