taki

C#でリモートのMySQLやSSH接続をやってみた


こんにちは、大阪スタジオ ソフトウェアエンジニアの滝です。
今回のお話は、C#でMySQLに接続してデータを取得したり、SSH接続でコマンドの送信をやってみたお話になります。

きっかけは、業務で集計ツールを作成する機会があり、その要件は、Windows環境からターミナルを使わずに、データベース及びログサーバーからデータ集計を行い、その結果をExcelファイルに出力するというものです。
実現には様々な方法が考えられますが、今回はWindows環境から行いたいということだったため、デスクトップクライアントを作成し、アプリケーションを実行することで集計処理を自動で行ない、結果をExcelファイルに出力するようなツールを作成することにしました。
要件を満たすために、C#でMySQLに接続してデータを取得したり、SSH接続でコマンドの送信を行う必要があり、どのように実現したかを簡単なサンプルコードと共に紹介させていただきます。

プロジェクトはコンソールアプリでもフォームアプリでもどちらでも動作します。
対応プラットフォームなど、詳しい情報は以下のリンクから確認できます。

 

今回使うライブラリ

・SSH.NET (github)
https://github.com/sshnet/SSH.NET

・MySQL(github)
https://dev.mysql.com/doc/connector-net/en/

 

SSH接続のやりかた

  • 必要なもの
    • SSH接続が可能なリモート環境(物理マシンでもVMでもOK)
      • 記事内で使用している環境はLinux CentOS 6.9(VirtualBox使用)
    • VisualStudio(どのエディションでも可)
      • 記事内で使用している開発環境はVisualStudio2017
  1. 新しいプロジェクトを作成または既存のプロジェクトを読み込む
    このサンプルではConnectSSHというプロジェクトをコンソールプロジェクトで作成しました。
    既存のプロジェクトでテストしていただいても問題ありませんが、できれば新規作成をおすすめします。

  2. NuGetからSSH.NETの追加
    図のように、ソリューションエクスプローラー内で1で作成したプロジェクト名の項目を右クリックし、NuGetパッケージの管理をクリックします。参照タブをクリックし、検索フォームに「SSH.NET」と入力し検索します。Renci製のSSH.NETをプロジェクトにインストールします

  3. とりあえず接続してみる
    以下にサンプルコードを記載します。コードの説明はコメントに書いております。

    using System;
    using Renci.SshNet;
    
    namespace RemoteServerConnect
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    // 接続先のホスト名またはIPアドレス
                    var hostNameOrIpAddr = "192.168.xxx.xxx";
    
                    // 接続先のポート番号
                    var portNo = 22;
    
                    // ログインユーザー名
                    var userName = "taki";
    
                    // ログインパスワード
                    var passWord = "xxxxxxxxxx";
    
                    // コネクション情報
                    ConnectionInfo info = new ConnectionInfo(hostNameOrIpAddr, portNo, userName,
                        new AuthenticationMethod[] {
                            new PasswordAuthenticationMethod(userName, passWord)
                            /* PrivateKeyAuthenticationMethod("キーの場所")を指定することでssh-key認証にも対応しています */
                        }
                    );
    
                    // クライアント作成
                    SshClient ssh = new SshClient(info);
    
                    // 接続開始
                    ssh.Connect();
    
                    if(ssh.IsConnected)
                    {
                        // 接続に成功した(接続状態である)
                        Console.WriteLine("[OK] SSH Connection succeeded!!");
                    }
                    else
                    {
                        // 接続に失敗した(未接続状態である)
                        Console.WriteLine("[NG] SSH Connection failed!!");
                        return;
                    }
    
                    // 接続終了
                    ssh.Disconnect();
                }
                catch(Exception ex)
                {
                    // エラー発生時
                    Console.WriteLine(ex);
                    throw ex;
                }
                
            }
        }
    }
    

    パスワード認証でSSH接続をしてみた例になります。
    接続できない場合は、ファイアウォールやポートフォワーディング設定など、ネットワーク接続自体に問題がないかを確認してみてください。

  4. コマンドを送信してみる(リモートマシンの時間を取得)
    using System;
    using Renci.SshNet;
    
    namespace RemoteServerConnect
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    // 接続先のホスト名またはIPアドレス
                    var hostNameOrIpAddr = "192.168.xxx.xxx";
    
                    // 接続先のポート番号
                    var portNo = 22;
    
                    // ログインユーザー名
                    var userName = "taki";
    
                    // ログインパスワード
                    var passWord = "xxxxxxxxxxxxxx";
    
                    // コネクション情報
                    ConnectionInfo info = new ConnectionInfo(hostNameOrIpAddr, portNo, userName,
                        new AuthenticationMethod[] {
                            new PasswordAuthenticationMethod(userName, passWord)
                            /* PrivateKeyAuthenticationMethod("キーの場所")を指定することでssh-key認証にも対応しています */
                        }
                    );
    
                    // クライアント作成
                    SshClient ssh = new SshClient(info);
    
                    // 接続開始
                    ssh.Connect();
    
                    if(ssh.IsConnected)
                    {
                        // 接続に成功した(接続状態である)
                        Console.WriteLine("[OK] SSH Connection succeeded!!");
                    }
                    else
                    {
                        // 接続に失敗した(未接続状態である)
                        Console.WriteLine("[NG] SSH Connection failed!!");
                        return;
                    }
    
                    // 送信したいコマンドを変数に入れる
                    var commandString = "date";
    
                    // コマンドを作成する
                    SshCommand cmd = ssh.CreateCommand(commandString);
    
                    // コマンドを実行する
                    Console.WriteLine("[CMD] {0}", commandString);
                    cmd.Execute();
    
                    // 結果を変数に入れる
                    var stdOut = cmd.Result;
                    var stdErr = cmd.Error;
    
                    // 終了コードを表示する
                    Console.WriteLine("終了コード:{0}", cmd.ExitStatus);
    
                    // 標準出力を表示する
                    if (stdOut != string.Empty)
                    {
                        Console.WriteLine("標準出力:");
                        Console.WriteLine(stdOut);
                        Console.WriteLine("---------");
                    }
    
                    // エラー出力を表示する
                    if (cmd.ExitStatus != 0 && stdErr != string.Empty)
                    {
                        Console.WriteLine("標準エラー出力:");
                        Console.WriteLine(stdErr);
                        Console.WriteLine("----------------");
                    }
    
                    // 接続終了
                    ssh.Disconnect();
                }
                catch(Exception ex)
                {
                    // エラー発生時
                    Console.WriteLine(ex);
                    throw ex;
                }           
            }
        }
    }
    

    今回はサンプルとして、リモート側の時間を取得してコンソールに表示するサンプルを作ってみました。

  5. こんなことに使えます
    SSHをターミナル以外のWindowsクライアントから送信するという事自体が滅多にないことだとは思いますが
    Webクライアントが使えない環境や、サーバーへのSSHアクセスはさせたいがコマンドをテンプレート化して使用させるなどの制限をしたい場合などに使えるかもしれません。
    もちろん、このライブラリを使ってターミナルクライアントを作ることも可能だと思います。

MySQL接続のやりかた

  • 必要なもの
    • MySQLサーバーが起動していて接続が可能なリモート環境(物理マシンでもVMでもOK)
      • 記事内で使用している環境はLinux CentOS 6.9 / MySQL5.5.50(VirtualBox使用)
    • VisualStudio(どのエディションでも可)
      • 記事内で使用している開発環境はVisualStudio2017
  1. 新しいプロジェクトを作成または既存のプロジェクトを読み込む
    このサンプルではConnectMysqlというプロジェクトをコンソールプロジェクトで作成しました。
    既存のプロジェクトでテストしていただいても問題ありませんが、できれば新規作成をおすすめします。

  2. NuGetからMysql.Dataの追加
    図のように、ソリューションエクスプローラー内で1で作成したプロジェクト名の項目を右クリックし、NuGetパッケージの管理をクリックします。参照タブをクリックし、検索フォームに「Mysql」と入力し検索します。
    Oracle製のMysql.Dataをプロジェクトにインストールします。

  3. とりあえず接続してみる
    以下にサンプルコードを記載します。コードの説明はコメントに書いております。

    using System;
    using MySql.Data.MySqlClient;
    
    namespace ConnectMysql
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 接続に必要なパラメータ文字列を生成する
                var connectionParams = string.Format(
                    "host={0}; userid={1}; password={2}; database={3}; charset={4}",
                    "192.168.xxx.xxx",
                    "taki",
                    "xxxxxxxxxxxx",
                    "sample_db",
                    "utf8"
                );
    
                // コネクターを作成
                var mysql = new MySqlConnection(connectionParams);
    
                try
                {
                    // 接続開始
                    mysql.Open();
                }
                catch (MySqlException ex)
                {
                    // 何らかの理由で接続に失敗した
                    Console.WriteLine(ex);
                    throw ex;
                }
            }
        }
    }
    

    リモートにインストールされたMySQLサーバーにアクセスする例になります。
    接続できない場合は、MySQLユーザーのアクセス権、ファイアウォールやポートフォワーディング設定など、設定に問題がないかを確認してみてください。

  4. insertとselectをしてみる
    using System;
    using MySql.Data.MySqlClient;
    
    namespace ConnectMysql
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 接続に必要なパラメータ文字列を生成する
                var connectionParams = string.Format(
                    "host={0}; userid={1}; password={2}; database={3}; charset={4}",
                    "192.168.xxx.xxx",
                    "taki",
                    "xxxxxxxxxxx",
                    "sample_db",
                    "utf8"
                );
    
                // コネクターを作成
                var mysql = new MySqlConnection(connectionParams);
    
                try
                {
                    // 接続開始
                    mysql.Open();
                }
                catch (MySqlException ex)
                {
                    // 何らかの理由で接続に失敗した
                    Console.WriteLine(ex);
                    throw ex;
                }
    
                // サンプルではこういうテーブルにアクセスしている
                // CREATE TABLE shoplineup(ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY, Name VARCHAR(16) NOT NULL, Price INT NOT NULL);
    
                // コマンド文字列用変数
                string cmd = "";
    
                // insertしてみる
                cmd = @"INSERT INTO shoplineup SET Name='Sword', Price=100;";
    
                // コマンドコンポーネント作成
                var insertCmd = new MySqlCommand(cmd, mysql);
    
                // 実行
                MySqlDataReader insertResult = insertCmd.ExecuteReader();
    
                // 変更を与えた件数を表示してみる
                Console.WriteLine("{0}row affected", insertResult.RecordsAffected);
    
                // 結果を閉じる
                insertResult.Close();
    
                // selectしてみる
                cmd = @"SELECT * FROM shoplineup;";
    
                
    
                // コマンドコンポーネント作成
                var selectCmd = new MySqlCommand(cmd, mysql);
    
                // 実行
                MySqlDataReader selectResult = selectCmd.ExecuteReader();
    
                // MySqlDataReaderというクラスに結果が入っている
                // Read()を呼ぶことで次の行にアクセスする
    
                while(selectResult.Read())
                {
                    var id = selectResult.GetInt32("ID");  // フィールド名でのアクセス
                    var name = selectResult.GetString(1);  // カラムインデックスでのアクセス
                    var price = selectResult.GetUInt32(2); // 同上
    
                    Console.WriteLine("ID:{0} Name:{1} Price:{2}", id, name, price);
                }
    
                // 結果を閉じる
                selectResult.Close();
    
                // Mysql接続終了
                mysql.Close();
            }
        }
    }
    

    今回はサンプルとして、sample_dbというデータベースにあるshoplineupテーブルに対してinsertおよびselectをする例を作ってみました。

  5. DataTableで欲しい場合

    var datatable = new DataTable();
    var adapter = new MySqlDataAdapter(query, mysql);
    adapter.Fill(datatable);
    return datatable;

    このようにして、クエリを実行する部分にMySqlDataAdapterを使用することで結果をDataTableで得ることも出来ます。

  6. こんなことに使えます
    MySQLターミナルクライアントを作ることも出来ますし、機能をテンプレート化してデータの集計用クライアントとしても使うことが出来ます。

まとめ

SSH接続及びMySQL接続をC#でやってみるというチャレンジでしたが、いかがでしたでしょうか?
パッケージのおかげで誰でも簡単に実装出来るので、アイディア次第ではとても便利なアプリを作ることが出来ると思います。
C#でSSH接続やMySQL接続をやってみたいという方のお役に立てれば幸いです。


taki

剣と魔法のログレスにおけるバックアップのお話


ご無沙汰しております、ソフトウェアエンジニアの滝です。
今回は、Webブラウザ版 『剣と魔法のログレス』 (以下ログレス)のデータバックアップのお話です。

1日あたり数GBという膨大なデータが出力されるため、どのようにしてバックアップを取りどのように保管しているかについてお話したいと思います。

何のバックアップを取っているか?

ログレスでは、お客様のサポートや動向調査のために、行動履歴データが含まれるログデータやデータベースのバックアップを行っています。

バックアップはいつとっているのか?

データベースは毎朝定期的にバックアップを出力しています。
所要時間は1時間程度ですが、1日につき数GBのダンプデータが出力されます。
ログデータは、ゲームサーバーが出力したものを取得します。
これらのバックアップファイルは、社内のログ管理サーバーにキャッシュされ、定期的に圧縮作業を行っています。

バックアップデータはどのように保管されているのか?

ログレスでは月に一度、LTO(磁気テープ)にバックアップデータを書き込みます。
バックアップするデータは毎月cronで月初に圧縮され、一時的に別のディスクスペースに置かれます。
バックアップが完了すればメールが届くので、内容を確認してLTOに書き込みます。
数年前のプロジェクトでは、当時DVD-Rを使ったバックアップを行っていましたが、技術が進歩し記録メディアの大容量化進んで単価が安くなったため
現在そのプロジェクトでは、ブルーレイディスクでのバックアップを取っています。
ログレスも2016年5月まではブルーレイディスクへバックアップをしておりました。
しかし、ログレスのバックアップデータは圧縮しても1月あたり約160GBありますので、ブルーレイディスクを複数枚に渡って書き込む必要があり、それらの作業を自動化するには難があったため、LTO化することになりました。

終わりに

簡単ではございますが、ログレスのバックアップのことについてお話させていただきました。
オンラインゲームを運営する上では、あまり表の話に出ないバックアップですが
バックアップは、データ復旧などのもしものことがあった時だけでなく
様々な調査や分析にも使われる大切なデータです。

今回は、現場の裏側のそのまた裏の話ということで、以上バックアップのお話でした。


taki

パネルディスカッション「神ゲーとクソゲー」


はじめましてこんにちは。
大阪スタジオ ソフトウェアエンジニアの滝です。
大阪スタジオエンジニア社内勉強会の運営をやっています。

今回のエンジニア勉強会では、大阪スタジオ社内でパネルディスカッションを行いました。
大阪スタジオ内の社内スタッフのみでパネルディスカッションを行うのは初の試みです。

osaka_paneldiscussion_20120220

続きを読む