3日坊主アプリ開発 210〜229日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 今回の作業

セーブ/ロード機能の実装

 

Simulator Screen Shot 2016.08.09 23.57.39

クラウドでのセーブ/ロード機能を実装しました。
AppleのiCloudではなく、独自サーバ上(さくらのVPS)に実装しています。

もちろん、クライアント(objective-c)の開発と共に、サーバ側のサーブレット(java)やユーザテーブル(Mysql)の作成も同時に行いました。

画像の「Change ID」ボタンは、スマホを買い替えた場合などデータを端末間で移動する場合に使用します。元の端末で表示されているIDを、新しい端末で「Change ID」ボタンにより入力すると、データが引き継げます。

iPadにも対応予定のゲームですが、iPhoneやiPad間でIDを同じに設定しておけば、どちらでも同じデータで遊ぶような使い方も可能です。

素晴らしい!

長いIDを入力しなきゃならないこと以外は!!

ID長い方が、何か有り難みがあるよね。うん。だから長くても大丈夫。

  • 明日の予定

対人戦画面の実装

3日坊主アプリ開発 209日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

DB接続管理クラスの作成

 

sql投げるだけで実行してくれるメソッドを持つクラスを作成しました。
コネクションを意識せずに使えるタイプと、自分でコネクションを渡せる2タイプを用意しました。

例外発生時のロギングには、昨日作成した自作クラスを使っています。

使うとき意味なくインスタンス化する必要あるし、ResultSet呼び出し元でcloseしなきゃいけないし、いまいちなので、もうちょっと改造します。

package servlet;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Statement;
import servlet.Logging;

public class DBManager {

        Connection getConn() {
                Connection conn = null;
                try {
                    Class.forName(“com.mysql.jdbc.Driver”);
                    // MySQLに接続
                    conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/catama”, “user”, “password”);
                } catch (ClassNotFoundException | SQLException e) {
                    Logging.out(e);
                }
                return conn;
        }

        void releaseConn(Connection conn) {
                if (null == conn) {
                        Logging.out(“connection is null.”);
                        return;
                }
                try {
                        conn.close();
                } catch (SQLException e) {
                        Logging.out(e);
                }
        }

        ResultSet query(String sql) {
                Connection conn = null;
                ResultSet rs = null;
                try {
                        conn = this.getConn();
                        rs = this.query(sql, conn);
                } finally {
                        releaseConn(conn);
                }
                return rs;
        }

        ResultSet query(String sql, Connection conn) {
                if (null == conn) {
                        Logging.out(“connection is null.”);
                        return null;
                }
                ResultSet rs = null;
                Statement st = null;

                try {
                        st = conn.createStatement();
                        rs = st.executeQuery(sql);
                } catch(SQLException e) {
                        Logging.out(e);
                } finally {
                        if (null != st) {
                                try {
                                        st.close();
                                } catch (SQLException e) {
                                        Logging.out(e);
                                }
                        }
                }
                return rs;
        }

        int queryExec(String sql) {
                int result = -1;
                Connection conn = null;
                try {
                        conn = this.getConn();
                        result = this.queryExec(sql, conn);
                } finally {
                        releaseConn(conn);
                }
                return result;
        }

        int queryExec(String sql, Connection conn) {
                int result = -1;
                if (null == conn) {
                        Logging.out(“connection is null.”);
                        return result;
                }
                Statement st = null;

                try {
                        st = conn.createStatement();
                        result = st.executeUpdate(sql);
                } catch (SQLException e) {
                        Logging.out(e);
                } finally {
                        if (null != st) {
                                try {
                                        st.close();
                                } catch (SQLException e) {
                                        Logging.out(e);
                                }
                        }
                }
                return result;
        }
}

  • 明日の予定

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

3日坊主アプリ開発 208日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

ロギングクラスの作成

 

スタックトレースやクラス名/メソッド名/エラー発生行番号を記録してくれるロギングクラスを作っていました。

package servlet;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Logging {

  static void out(Exception e) {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”);

    System.out.println(“— ” + sdf.format(date) + ” —“);
    e.printStackTrace();
    System.out.println(“— ” + sdf.format(date) + ” —“);
  }

  static void out(String msg) {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”);
    StackTraceElement throwableStackTraceElement = new Throwable().getStackTrace()[1];

    System.out.println(“— ” + sdf.format(date) + ” —“);
    System.out.println(msg + “at ” + throwableStackTraceElement.getClassName() + “#” + throwableStackTraceElement.getMethodName() + “(” + throwableStackTraceElement.getLineNumber() + “)”);
    System.out.println(“— ” + sdf.format(date) + ” —“);
  }
}

outメソッドに例外を渡すと、

— 2016/07/19 23:29:41 —
java.sql.SQLException: Field ‘prize_panels’ doesn’t have a default value
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:963)
〜略〜
at servlet.DBManager.queryExec(DBManager.java:96)
at servlet.DBManager.queryExec(DBManager.java:79)
at servlet.Login.doGet(Login.java:38)
〜略〜
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
— 2016/07/19 23:29:41 —

のように、日時とスタックトレースを出力します。

outメソッドに文字(エラーが発生しましたよ)を渡すと

— 2016/07/19 23:29:41 —
エラーが発生しましたよ at servlet.DBManager#getConn(13)
— 2016/07/19 23:29:41 —

のように、日時と発生クラス名/メソッド名/行番号を出力します。

メソッドの中身を書き換えて”コンパイル”すれば、120%自由自在にカスタマイズできます。

シンプル!簡単!分かり易い!

え?commons-loggingとかlog4jとかslf4j-apiとかjcl-over-slf4jとかlogback-classicとか使え?

“設定”覚えるのがめんどいんで結構です。

  • 明日の予定

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

3日坊主アプリ開発 207日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。
※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

 

DBにユーザ情報を格納する前に、DBコネクションの管理や、SQL実行を簡単にする独自DBマネージャクラスを作成していました。様々な処理のベースとなるクラスなのでちょっと時間をかけて作っています。

viコマンドでゴリゴリ書いて、javacコマンドでクラスパス通してコンパイルしています。

eclipseを使え? それほど大量のクラスを作るつもりはないので、手動コンパイルで十分です。たぶん。

  • 明日の予定

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

3日坊主アプリ開発 206日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

 

ちょっとトラブったので、今日はあまり進捗ありませんでした。やったことを書き殴ります。

まず、mysqlに接続するために、jdbcドライバダウンロードしました。

http://dev.mysql.com/downloads/connector/j/

このページから持ってきたtarファイルを解凍して、中身の

mysql-connector-java-5.1.39-bin.jar

を、webapps/catama/WEB-INFの下にlibディレクトリ作って、そこにコピーしました。

これで、この”catama”のWebアプリケーション内で使えるようになります。

次に、javaのサーブレットから接続してみます。

Connection con = null;
String dbResult = “start MySQL connection.”;
String newLine = “
“;
try {
Class.forName(“com.mysql.jdbc.Driver”);
// MySQLに接続
con = DriverManager.getConnection(“jdbc:mysql://localhost:3306/catama”, “USER”, “PASSWORD”);
dbResult += newLine + “connected to MySQL.”;
} catch (ClassNotFoundException | SQLException e) {
dbResult += newLine + e.toString();
} finally {
if (con != null) {
try {
con.close();
dbResult += newLine + “closed MySQL.”;
} catch (SQLException e) {
dbResult += newLine + e.toString();
}
}
}
System.out.println(dbResult);

しかし、なぜか接続に失敗します。

さんざん悩みまくった挙句、netstatコマンドを使うと、mysqlの待ち受けポートである3306がリストに出てきません。

そこでmy.cnfを確認すると、以下の記述を見つけました。

skip-networking

この行が原因でした。この行をコメントアウトして、無事接続できました。

  • 明日の予定

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

3日坊主アプリ開発 205日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

iOSアプリからサーブレットにアクセスする際、どちらが単位時間当たりの処理数が多いか確認する。

1.tomcatのサーブレットにアクセスする。
2.nginx経由でtomcatのサーブレットにアクセスする。

 

1つのリクエストが完了したら即座に次のリクエストを投げ、合計100回繰り返す”直列方式”と、0.01秒間隔で100リクエストを投げる”並列方式”の2パターン計測しました。

結果は以下の通りです。

1.tomcatのサーブレットにアクセス

直列:21.186秒
並列:0.501秒

2.nginx経由でtomcatのサーブレットにアクセス

直列:15.162秒 (1の72%)
並列:0.352秒 (1の70%)

nginxを経由した方が3割分ほど速くなりました。尚、リクエストはキャッシュされないよう100個全て異なるものを投げています。nginxの設定はほぼデフォルトです。誰かnginxの方が速くなる理由を教えやがって下さい。

  • 明日の予定

サーバにログインする処理を実装する。サーバに各ユーザの識別ID※とユーザ名を格納する。

※appleの推奨するidentifierForVendor(IDFV)を識別IDとして使います。

3日坊主アプリ開発 204日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

サーバ側のSSL処理を見直す。

 

iOSのセキュリティ要件が高くてなかなかサーバに接続できていませんでしたが、今日やっとiOSアプリ内から自分のサーバーに接続できました!!

tomcatとnginxのSSL接続について、以下を確認しました。

・TLS1.2をサポートしていることを確認しました。
・暗号化スイートについて、appleの要求するリスト中の TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 を使えることを確認しました。
・利用されるサーバ証明書はSHA256以上のハッシュアルゴリズムによって署名されており、2048ビット以上のRSA 鍵が使われていることを確認しました。

ただし、自己証明書を使っているため、上記要件を満たしても接続できず、結局Info.plisに以下の記述を加えて回避しました。

<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>minnano.app</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>

http通信も可能な設定なのですが、実際にはhttpsで通信します。

アプリがリジェクト食らうようなら、SSL証明書を買う予定です。

  • 明日の予定

iOSアプリからサーブレットにアクセスする際、どちらが単位時間当たりの処理数が多いか確認する。

1.tomcatのサーブレットにアクセスする。
2.nginx経由でtomcatのサーブレットにアクセスする。

3日坊主アプリ開発 203日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 今回の作業

iOSアプリからサーブレットにアクセスする際、以下3パターンでどれが一番、単位時間当たりの処理数が多いか確認する。

1.httpでtomcatのサーブレットにアクセスする。
2.httpsでtomcatのサーブレットにアクセスする。
3.httpsでnginx経由でサーブレットにアクセスする。

ええと、今日は上記3パターンの通信を実装しようとしたのですが、iOS9では、デフォルトでhttp通信は出来ないようです。なので1は除外します。また。SSL通信についても、要件が厳格で、サーバ側の処理を見直さないと通信できないようです。

・・・

・・

  • 明日の予定

サーバ側のSSL処理を見直す。

3日坊主アプリ開発 186〜202日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 今回の作業

対人戦のためのサーバ側処理実装準備
・・・

・・

サボってごめんなさい。日記も作業も。

これからは毎日ちょっとずつでも、殴り書きでも、作業内容を報告します。

作業しなかった日は正直に何もしてませんって報告します・・・

当初、100日あれば余裕でアプリリリースできると思ってたら、もう200日超えちゃった・・・

( ゚∀゚)

・・・

・・

気をとりなおして。

ここ2週間ほどは、対人戦のためのサーバ側処理の準備ということで、CentOSに色んなものをセットアップしました。

以下箇条書きします。

▪️Java入れました。

# yum install java-1.8.0-openjdk
# yum install java-1.8.0-openjdk-devel

▪️Tomcat入れました。

# wget http://ftp.kddilabs.jp/infosystems/apache/tomcat/tomcat-8/v8.0.36/bin/apache-tomcat-8.0.36.tar.gz
# tar -xvzf apache-tomcat-8.0.36.tar.gz
# mkdir /opt/tomcat
# mv apache-tomcat-8.0.36 /opt/tomcat

# vi /etc/profile
JRE_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.91-1.b14.el6.x86_64/jre
CATALINA_HOME=/opt/tomcat/apache-tomcat-8.0.36
export JRE_HOME CATALINA_HOME

・・・

SSLの設定や管理画面のIP接続制限設定なども行いました。デフォルトポート8080を変更したりもしました。ちゃんとtomcat起動専用ユーザ作って、tomcatを起動するようにしました。

単純なサーブレット作って動作確認しました。

package servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* テストサーブレットクラス
*/
@WebServlet(description = “サーブレット テスト”, urlPatterns = { “/Login” })
public class Login extends HttpServlet {

/**
* GETリクエスト処理
*/
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// レスポンスヘッダの設定
response.setContentType(“text/html; charset=UTF-8”);
// リクエストパラメタの取得
String name = request.getParameter(“name”);
// ページの描画
PrintWriter writer = response.getWriter();
writer.println(“<html>”);
writer.println(” <head>”);
writer.println(” <title>Login</title>”);
writer.println(” </head>”);
writer.println(” <body>”);
writer.println(” <p>” + name + “</p>”);
writer.println(” </body>”);
writer.println(“</html>”);
}
}

 

# javac -classpath /opt/tomcat/apache-tomcat-8.0.36/lib/servlet-api.jar Login.java

コンパイルしてtomatのwebappsの下のWEB-INFの下のclassesの下にservletフォルダ作ってclassコピーして動作することを確認しました。

WEB-INFフォルダにはweb.xmlファイルを作成して設置しました。

# vi web.xml
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<web-app xmlns:xsi=”http://www.w3c.org/2001/XMLSchema-instance”>
<display-name>Catama</display-name>
</web-app>

▪️nginx入れました。

# touch /etc/yum.repos.d/nginx.repo
# vi /etc/yum.repos.d/nginx.repo

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

# yum install nginx

nginxでもSSLの設定をしました。nginx経由でtomcat上のサーブレットを見に行くように設定しました。

また、wiresharkを使って、httpsアクセスによってリクエストパラメタの内容等がちゃんとわからないことを確認しました。

  • 明日の予定

iOSアプリからサーブレットにアクセスする際、以下3パターンでどれが一番、単位時間当たりの処理数が多いか確認しようと思います。

1.httpでtomcatのサーブレットにアクセスする。
2.httpsでtomcatのサーブレットにアクセスする。
3.httpsでnginx経由でサーブレットにアクセスする。

3日坊主アプリ開発 165〜185日目


    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

3日坊主で終わらないために始めた3日坊主アプリ開発日記ですが、3日を大きく上回り、なんと164日続きました。

自分としては大記録です。
短い間でしたがみなさんありがとうございました。

・・・

・・

いやごめんなさい終わらないです。

ちょっと20日ほど、アプリで稼いだお金を使って別世界に行ってました。

アプリで稼いだお金は臨時収入なので、完全に自由に、勝手に好きなことに何にでも使えるのでとても嬉しいです。

もう稼いだ分、全部使い切っちゃったけど。

今日からまた開発頑張ります・・・

 

今日は対人戦用のテーブルが足りないことに気づいたので追加していました。
また、クライアントからサーバにアクセスする際のインターフェイスを考えていました。

別に仕事じゃないので、ちゃんと設計しないで行き当たりバッタリ実装の方が面白くて良いかな。

自分が楽しいことが続くコツだと思います。

・テーブル

[支配者テーブル]

内容:

争って勝ち取る15ステージの支配者情報を保持する。

カラム:

ステージID,ユーザID,連勝回数,勝利者コメント

・サーバアクセス

[支配者情報取得]

対戦ステージ選択画面用に、対戦ステージの支配者情報を取得する。

[対戦相手情報取得]

対戦ステージを選択後、対戦相手のロジック情報などを取得する。

[対戦結果送信]

対戦後、勝敗結果をサーバに送信する。

・・・だめだ、なんかER図とか書きたくなってきた。

  • 明日の予定

対人戦の実装 サーバ処理

大手には作れないアプリを(気持ちだけは(๑•̀ㅂ•́)و✧)