スマフォアプリ(java/kotlin/Swift/Unity)からサーバ(java)のmariadbを操作する.5
●前提
同時接続は多数
サーバ言語:java+mariaDB+JDBCドライバ
MySQLのrootユーザー名root,passwordは1234abcde
クライアント言語:android-java/android-kotlin/iOS-Swift/unityC#
通信ポート:3456
今回はやっとandroid端末からのアクセスができるようにします。
androidでsocketなのでスレッドを生成して(socketは非同期通信なので)そこでjavaのsocketライブラリを使っています(今現在、androidライブラリにsocketライブラリはありません)。
AndroidManifest.xmlに以下の分を追加してください。
<uses-permission android:name="android.permission.INTERNET" />
activity_main.xmlは、以下のようになっています。
LinearLayoutに、一個だけScrollViewを入れています。順位の結果用で最大99人分見れるようにしたのでスクロール対応にしています。
縦画面しか考えていない配置ですが、サンプルなのでご勘弁ください。本来ならばactivityを二つ用意して切り替えるかポートレート固定とかが良いでしょう。
javaのソースコードは、MainActivity.javaとSocketClient.javaの2個に分けています。
それ以外は特に特殊なことはしていないと思います。
そして、ScoreServer.javaとScoreClient.javaは正規表現で間違いがあったので修正しました。
今回はそんなところです。
[activity_main.xml]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btnConnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="接続" />
<Button
android:id="@+id/btnDisconnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="切断" />
<EditText
android:id="@+id/editTop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="何番目の人から見ますか?(1以上)"
android:inputType="number" />
<EditText
android:id="@+id/editCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="見たい人数を入力して下さい(1以上99以下)"
android:inputType="number" />
<Button
android:id="@+id/btnScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="指定された順位から指定された人数分のスコア表示" />
<EditText
android:id="@+id/editName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="名前を入力して下さい"
android:inputType="textPersonName" />
<EditText
android:id="@+id/editScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="スコアを入力して下さい"
android:inputType="number" />
<Button
android:id="@+id/btnInsert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="スコアを登録します" />
<ScrollView
android:id="@+id/ScrollView01"
android:layout_width="match_parent"
android:layout_height="320dp"
android:layout_alignParentLeft="true"
android:fastScrollEnabled="true" >>
<TextView
android:id="@+id/txtResult"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
</ScrollView>
</LinearLayout>
[MainActivity.java]
package com.example.apk0010;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 接続ボタンを押されたら接続する
Button btn = findViewById(R.id.btnConnect);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SocketClient.connect();
}
});
// 切断ボタンを押されたら切断する
btn = findViewById(R.id.btnDisconnect);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SocketClient.disconnect();
}
});
// 指定された順位から指定された人数分のスコア表示
btn = findViewById(R.id.btnScore);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideInput(); // 入力を消す
EditText editTop = findViewById(R.id.editTop);
EditText editCount = findViewById(R.id.editCount);
String cmdScore = "2," + editTop.getText().toString() + "," + editCount.getText().toString();
Toast.makeText( getApplicationContext(),cmdScore,Toast.LENGTH_LONG).show();
if(cmdScore.matches("2,[1-9][0-9]{0,},[1-9][0-9]{0,1}")){
// 返り値の初期化
SocketClient.initStr();
// score習得コマンドの送信
SocketClient.sendStr(cmdScore);
// 返り値を取得(大体一秒だけ待つ
TextView textView = findViewById(R.id.txtResult);
textView.setText("スコア取得中");
String scoreLine = SocketClient.getStr(100);
if(scoreLine==null){
// タイムアウトした場合
textView.setText("スコア取得失敗!");
} else {
//
String score[] = scoreLine.split(",");
String strScore=score[0]+"人\n";
if(score[0]!="0"){
for(int c=1;c<score.length;c+=2){
strScore = strScore + "score:" + score[c] + " name:" + score[c+1] + "\n";
}
}
textView.setText(strScore);
}
// 返り値の初期化
SocketClient.initStr();
}
}
});
// スコアの登録
btn = findViewById(R.id.btnInsert);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideInput(); // 入力を消す
EditText editName = findViewById(R.id.editName);
EditText editScore = findViewById(R.id.editScore);
String cmdScore = "3," + editScore.getText().toString() + "," + editName.getText().toString();
Toast.makeText( getApplicationContext(),cmdScore,Toast.LENGTH_LONG).show();
if(cmdScore.matches("3,[0-9]{1,},[^ ]{1}.{0,31}")){
SocketClient.sendStr(cmdScore);
}
}
});
}
// 入力を消す
private void hideInput() {
if (getCurrentFocus() != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
// 画面が破棄される前の処理
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
TextView txtResult = findViewById(R.id.txtResult);
outState.putString("txtResult", txtResult.getText().toString());
}
// 画面の復元の処理
@Override
protected void onRestoreInstanceState(Bundle savedState) {
super.onRestoreInstanceState(savedState);
TextView txtResult = findViewById(R.id.txtResult);
txtResult.setText(savedState.getString("txtResult"));
}
}
[SocketClient.java]
package com.example.apk0010;
import android.util.Log;
import java.io.InputStream;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;
import static android.os.SystemClock.sleep;
class SocketClient {
private static Socket socket = null; // socket
private static final String SERVER_URL = "219.117.194.51"; // うちの会社のIPアドレス。"localhost" とかでも実験は出来ます
private static final int SOCKET_PORT = 3456;
private static BufferedReader in = null;
private static InputStream inp = null;
private static PrintWriter out = null;
private static String cmdStr=null;
private static String resData=null;
// スレッドを生成して接続
public static void connect(){
if(socket!=null) return;
//スレッド生成
(new Thread(){
public void run(){
// Log.d("socket","スレッド開始");
// 接続
try{
Log.d("socket","接続します");
socket = new Socket(SERVER_URL, SOCKET_PORT);
Log.d("socket","接続しました" + socket.getRemoteSocketAddress());
inp =socket.getInputStream();
in = new BufferedReader(new InputStreamReader(inp));
out = new PrintWriter(socket.getOutputStream(), true);
} catch (IOException e) {
Log.d("socket","接続失敗(IOException)");
} catch (Exception e){
Log.d("socket","接続失敗(Exception)");
}
// 送受信ループ
try{
while(socket!=null && socket.isConnected()){
// データの送信
if(cmdStr!=null) {
out.write(cmdStr);
out.flush();
cmdStr=null;
}
//データの受信
if (inp.available()!=0) {
//受信データがあれば、BufferedReaderのreadLine()で1行読取。
resData = in.readLine();
}
}
// } catch (IOException e) {
//Log.d("socket","送受信失敗(IOException)");
} catch (Exception e){
Log.d("socket","送受信失敗(Exception)");
} finally {
disconnect();
}
// Log.d("socket","スレッド終了");
}
}).start();
}
// 文字列を送る
public static void sendStr(String strCmd ){
try {
//データの送信
if (socket != null && socket.isConnected() && out!=null) {
cmdStr = strCmd + "\n";
} else {
Log.d("socket","送信失敗(Exception)");
disconnect();
}
}catch(Exception e){
Log.d("socket","送信失敗(Exception)");
disconnect();
}
}
// socket 接続させているか
public static boolean isConnected(){
if(socket==null || !socket.isConnected() ){
return false;
}
return true;
}
// 文字列初期化
public static void initStr(){
cmdStr = null;
resData = null;
}
// 文字列をもらう(タイムアウト付き)
// remarks
// 本来ならば文字列を呼び出し側に渡す場合は、コールバックで呼び出し側等に知らせる必要がある。これだけ規模が小さいならば、メインにのスコアボタンのメソッドを送信と受信に分けて文字列受け取りソッドを新設して呼び出し、そこでスコアセットしてもいい。
public static String getStr(int num){
if (socket != null && socket.isConnected()) {
do {
if (resData != null) {
Log.d("socket", resData);
return resData;
}
sleep(10);
num--;
} while (num > 0);
}
return null;
}
//切断
public static void disconnect(){
initStr();
try{
socket.close();
socket=null;
Log.d("socket","切断成功");
// remarks
// 本来ならばここでコールバックで呼び出し側等に知らせる必要がある。これも規模が小さければメインを直接呼び出してもいい。
}catch(Exception e){
Log.d("socket","切断失敗(Exception)");
socket=null;
}
}
}
[ScoerClient.java]
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;
public class ScoreClient {
public static final String SERVER_URL = "219.117.194.51"; // うちの会社のIPアドレス。"localhost" とかでも実験は出来ます
public static final int SOCKET_PORT = 3456;
public static void main(String args[]) {
Socket socket = null;
try {
System.out.println("接続します");
socket = new Socket(SERVER_URL, SOCKET_PORT);
System.out.println("接続しました" + socket.getRemoteSocketAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader keyIn = new BufferedReader(new InputStreamReader(System.in));
String input;
System.out.println("0[enter]で終了します。\n1[enter]でユーザー情報をスコアの高い順から全部出力\n2,num1,num2[enter]でnum1番目からnum2人分出力\n3,nun,name[enter]で得点numでnameを登録");
while ( (input = keyIn.readLine()).length() > 0 ) {
out.println(input);
String line = in.readLine();
if (line != null) {
if( input.equals("0")){
System.out.println("0[enter]が入力されたので終了します");
break;
} else if( input.equals("1")){
System.out.println("1が入力されました。ハイスコアを表示します");
System.out.println(line);
} else if( input.matches("2,[1-9][0-9]{0,},[1-9][0-9]{0,}") ){
System.out.println("2が入力されました。指定された順位から指定された人数分のスコアを表示します");
System.out.println(line);
} else if( input.matches("3,[0-9]{1,},[^ ]{1}.{0,31}") ){
System.out.println("3が入力されました。スコアを登録しました");
System.out.println(line);
} else {
System.out.println(line);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
socket=null;
}
} catch (IOException e) {}
if(socket!=null) System.out.println("切断されました " + socket.getRemoteSocketAddress());
}
}
}
[ScoerServe.java]
import java.util.ArrayList;
import java.sql.*;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;
//
// ハイスコアの管理サーバ(ctl+cで終了)
//
//----------------------------------------------------------------------------
class SqlCtl {
//
// sql control class
//
static Connection con = null;
private static final String MYSQL_URL = "jdbc:mysql://localhost:3306/test906"; // 接続するMySQLのURL(ローカルホスト:madiadb/MySQLは3306番ポート)
private static final String MYSQL_USR = "root"; // MySQLのユーザー名
private static final String MYSQL_PASS = "1234abcde"; // MySQLのユーザーのパスワード
//
// MySQL(mariaDB)に接続
//
public static boolean openSQL() {
try {
// mariaDBへ接続するためにJDBCドライバ読み込む
Class.forName("org.mariadb.jdbc.Driver").newInstance();
// 接続する
con = DriverManager.getConnection(MYSQL_URL, MYSQL_USR,MYSQL_PASS);
System.out.println("MySQL(mariaDB)に接続できました");
return true;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
System.out.println("JDBCドライバのロードに失敗しました");
} catch (SQLException e) {
System.out.println("MySQL(mariaDB)に接続できませんでした");
if (con != null) {
try {
con.close();
con=null;
} catch (SQLException ex) {
System.out.println("MySQL(mariaDB)のCloseに失敗しました");
}
}
}
return false;
}
//
// 値を返さないsqlクエリを実行
//
private static boolean execUpdate(String sql) {
try {
Statement stm = con.createStatement();
stm.executeUpdate(sql);
stm.close(); // close
return true;
} catch (SQLException e) {
return false;
}
}
//
// スコア順に取得する(先頭に人数が入ります)
//
public static String getScoreRank( String outLines ) {
try {
//
// データを取得するには Statement を作成して executeQuery() でSQL文を投げると ResultSet の object で返ってくる
//
Statement stm = con.createStatement();
ResultSet rs = stm.executeQuery("SELECT * FROM hiscore ORDER BY score DESC,id ASC");
String line="";
// rs.next()はこのカラムがあるかだけでは無く、次のカラムに移動させるので、このような方法をとっています。
int c=0;
while(rs.next()){
c++;
line += "," + "id:" + rs.getInt("id") + ":name=" + rs.getString("name") + ":score=" + rs.getInt("score");
}
outLines = c + line;
rs.close(); // close
stm.close(); // close
return outLines;
} catch (SQLException e) {
System.out.println("MySQL(mariaDB)に接続できませんでした3");
}
return outLines;
}
//
// 指定された順位から指定された人数分を、スコア順に取得する(スコアと名前だけをcsv形式で、先頭に人数が入ります)
//
public static String getScoreRank( String outLines,int top,int count ) {
// top(1org)からcount(1org)分取得する。
if( (top<1) || (count<1) ){
outLines="0";
return outLines;
}
try {
//
// データを取得するには Statement を作成して executeQuery() でSQL文を投げると ResultSet の object で返ってくる
//
Statement stm = con.createStatement();
ResultSet rs = stm.executeQuery("SELECT * FROM hiscore ORDER BY score DESC,id ASC");
String line="";
// rs.next()はこのカラムがあるかだけでは無く、次のカラムに移動させるので、このような方法をとっています。
int c=0;
int outCount=0;
while(rs.next() && count!=0 ){
c++;
if(c>=top){
line += "," + rs.getInt("score") + "," + rs.getString("name");
count--;
outCount++;
}
}
outLines = outCount + line;
rs.close(); // close
stm.close(); // close
return outLines;
} catch (SQLException e) {
System.out.println("MySQL(mariaDB)に接続できませんでした3");
}
return outLines;
}
//
// スコアと名前をデータベースに登録
//
public static void insertScore( int score, String name) {
String insert = "INSERT INTO hiscore (name,score) VALUES('" + name + "'," + score + ")";
if(!execUpdate(insert)){
System.out.println("追加出来ませんでした");
}
}
}
//----------------------------------------------------------------------------
//
// スレッド
//
class ScoreThread extends Thread {
private Socket socket;
public ScoreThread(Socket socket) {
this.socket = socket;
System.out.println("接続されました " + socket.getRemoteSocketAddress());
}
// run()は自動的に実行される
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String line;
while ( (line = in.readLine()) != null ) {
System.out.println(socket.getRemoteSocketAddress() + " 受信:" + line);
// クライアントから1が入力された場合はハイスコア表示
if(line.equals("1")){
line="";
line = SqlCtl.getScoreRank(line);
out.println(line);
System.out.println(socket.getRemoteSocketAddress() + " 送信:" + line);
}else if(line.matches("2,[1-9][0-9]{0,},[1-9][0-9]{0,}") ){
// 正規表現をくぐり抜けているので、パラメータ(line)にエラーは無いものとする。
String[] csvData = line.split(",");
line="";
line = SqlCtl.getScoreRank(line,Integer.parseInt(csvData[1]),Integer.parseInt(csvData[2]));
out.println(line);
System.out.println(socket.getRemoteSocketAddress() + " 送信:" + line);
}else if(line.matches("3,[0-9]{1,},[^ ]{1}.{0,31}") ){
// 正規表現をくぐり抜けているので、パラメータ(line)にエラーは無いものとする。
String[] csvData = line.split(",");
line = "scoreが" + csvData[1] + "の" + csvData[2] + "さんを登録します";
SqlCtl.insertScore( Integer.parseInt(csvData[1]),csvData[2] );
out.println(line);
System.out.println(socket.getRemoteSocketAddress() + " 送信:" + line);
} else {
out.println(line+"は間違ったコマンドです");
System.out.println(socket.getRemoteSocketAddress() + " 送信(error-cmd): " + line);
}
}
System.out.println("クライアントは終了しました" + socket.getRemoteSocketAddress());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
System.out.println("socketをcloseします" + socket.getRemoteSocketAddress());
socket.close();
socket=null;
}
} catch (IOException e) {}
if(socket!=null) {
System.out.println("切断されました" + socket.getRemoteSocketAddress());
socket=null;
}
}
}
}
//----------------------------------------------------------------------------
//
// ソケットサーバとマルチスレッドでの待ち受け(ctl+cで終了)
//
class MultiSocket {
public static final int SOCKET_PORT = 3456;
public static boolean cmdQuit = false;
public static boolean socketMain() {
// serversocket
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(SOCKET_PORT);
System.out.println("サーバーが起動しました(port="+ serverSocket.getLocalPort() + ")");
System.out.println("サーバーを終了する場合は [ctrl]+[c] で終了させて下さい");
// 待ち受け(ctrl+cで終了)
String input;
serverSocket.setSoTimeout( 1000 );
while (true) {
try {
Socket socket = serverSocket.accept(); // 待ち受け許可
new ScoreThread(socket).start(); // クライアントに接続されたらスレッド作成
} catch ( java.net.SocketTimeoutException ex ) {
if( cmdQuit ) {
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
serverSocket = null;
}
} catch (IOException e) {}
}
return false;
}
}
//----------------------------------------------------------------------------
//
// メイン
//
public class ScoreServer {
public static void main(String[] args) {
// データベースへ接続する
if(!SqlCtl.openSQL()){
System.out.println("データベースに接続できませんでした");
return;
}
// マルチクライアントソケットサーバの起動
if(!MultiSocket.socketMain()){
System.out.println("マルチクライアントソケットサーバの起動に失敗しました");
return;
}
}
}
次はandroid-kotlinからのアクセスを行います。
戻る