質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

UDP

UDP(User Datagram Protocol)とは、トランスポート層のプロトコルであり、コネクション型のデータサービスです。IPネットワーク上の別のホストにコンピュータのアプリケーションがメッセージを送ることができ、転送チャンネルやデータ経路を設定する必要はありません。TCPに比べて高速であるが、信頼性が薄いという特徴があります。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Q&A

解決済

1回答

1612閲覧

UDP通信にてボタン押下で文字列を切り替えながら連続で出力する方法

roatt

総合スコア45

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

UDP

UDP(User Datagram Protocol)とは、トランスポート層のプロトコルであり、コネクション型のデータサービスです。IPネットワーク上の別のホストにコンピュータのアプリケーションがメッセージを送ることができ、転送チャンネルやデータ経路を設定する必要はありません。TCPに比べて高速であるが、信頼性が薄いという特徴があります。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

0グッド

0クリップ

投稿2021/12/17 08:58

前提・実現したいこと

Android Studioを使用して、AndroidからPCへ以下の条件に沿った出力値をUDP通信で連続して出力するシステムを作成したいです。
・Button1が1度押下された場合、「A」が出力され、この値は0.1秒間保持される
・Button2が1度押下された場合、「B」が出力され、この値は0.1秒間保持される
・Button3が1度押下された場合、「C」が出力され、この値は0.1秒間保持される
・Buttonが押下されていない場合、「1」が出力され続ける

現状のプログラムとして
・アプリケーションが起動したときに、「1」が出力
・Button1が1度押下された場合、「A」が出力
・Button2が1度押下された場合、「B」が出力
・Button3が1度押下された場合、「C」が出力
までUDP通信で出力値を送信することができましたが、連続で出力する方法や出力値を保持する方法が分からず困っております。

※出力値はUdpIpToolで見ています。

該当のソースコード

MainActivity

Java

1public class MainActivity extends AppCompatActivity { 2 3 private static int PORT = 34567; //ポート宣言 4 DatagramSocket soc; //ソケット 5 PrintWriter printWriter; //ソケット送信用 6 String text; 7 int a = 1; 8 9 @Override 10 protected void onCreate(Bundle savedInstanceState) { 11 super.onCreate(savedInstanceState); 12 setContentView(R.layout.activity_main); 13 14 //サーバー用にソケットを作成し、ポートを設定する 15 try { 16 soc = new DatagramSocket(34567); 17 soc.bind(new InetSocketAddress(PORT)); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 22 23 //接続されたら"1"を出力 24 SendSocTask sendSocTask = new SendSocTask(10); 25 sendSocTask.execute(); 26 text ="1"; 27 28 Button button1 = findViewById(R.id.button1); 29 button1.setOnClickListener(new View.OnClickListener() { 30 @Override 31 public void onClick(View view) { 32 SendSocTask sendSocTask = new SendSocTask(1); 33 sendSocTask.execute(); 34 text ="A"; 35 } 36 }); 37 38 Button button2 = findViewById(R.id.button2); 39 button2.setOnClickListener(new View.OnClickListener() { 40 @Override 41 public void onClick(View v) { 42 SendSocTask sendSocTask = new SendSocTask(2); 43 sendSocTask.execute(); 44 text ="B"; 45 } 46 }); 47 48 Button button3 = findViewById(R.id.button3); 49 button3.setOnClickListener(new View.OnClickListener() { 50 @Override 51 public void onClick(View v) { 52 SendSocTask sendSocTask = new SendSocTask(3); 53 sendSocTask.execute(); 54 text ="C"; 55 } 56 }); 57 } 58 59 class SendSocTask extends AsyncTask<InetSocketAddress, Void, Void> { 60 61 private int sendValue; 62 63 //コンストラクタ 64 public SendSocTask(int value) { sendValue = value; } 65 66 @Override 67 protected Void doInBackground(InetSocketAddress... inetSocketAddresses) { 68 try { 69 byte[] ms = text.getBytes(); 70 DatagramPacket packet = new DatagramPacket(ms, ms.length, new InetSocketAddress(InetAddress.getByName("PCのIPアドレス"),PORT)); 71 soc.send(packet); 72 } 73 catch (Exception e) { 74 } 75 finally { 76 try { 77 //通信後、Socket接続を閉じ、関連付けられたすべてのリソース解放 78 if (a == 0) { 79 printWriter.close(); 80 } 81 if (a==0) { 82 } 83 } 84 catch (Exception e) { 85 e.printStackTrace(); 86 } 87 } 88 return null; 89 } 90 } 91}

activity_main

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".MainActivity"> 8 9 10 <Button 11 android:id="@+id/button1" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:layout_marginEnd="10dp" 15 android:text="Button" 16 app:layout_constraintBaseline_toBaselineOf="@+id/button2" 17 app:layout_constraintEnd_toStartOf="@+id/button2" 18 app:layout_constraintStart_toStartOf="parent" /> 19 20 <Button 21 android:id="@+id/button2" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:layout_marginEnd="10dp" 25 android:layout_marginBottom="324dp" 26 android:text="Button" 27 app:layout_constraintBottom_toBottomOf="parent" 28 app:layout_constraintEnd_toStartOf="@+id/button3" 29 app:layout_constraintStart_toEndOf="@+id/button1" /> 30 31 <Button 32 android:id="@+id/button3" 33 android:layout_width="wrap_content" 34 android:layout_height="wrap_content" 35 android:layout_marginEnd="1dp" 36 android:text="Button" 37 app:layout_constraintBaseline_toBaselineOf="@+id/button2" 38 app:layout_constraintEnd_toEndOf="parent" 39 app:layout_constraintStart_toEndOf="@+id/button2" /> 40</androidx.constraintlayout.widget.ConstraintLayout>

試したこと

soc.send(packet);やclass SendSocTask内のコードにwhile(true)を挟み込み、無理に無限ループで出力してみましたが、
タブレットの画面は真っ白になり(正常に表示される場合もありました)、UdpIpToolのログも当たり前ですが「1」だけ出力され、「切断」をクリックしても無限ループし続けていました。

補足情報(FW/ツールのバージョンなど)

上記にも記載しましたが、Android studioを使用し、javaで作成しています。
どうにか実現したい事ができないかインターネットで調べてみましたが、
まったく実現方法がわかりませんでした…
お手数をおかけしますが、プログラミングの力も未熟なため丁寧に説明もいただけると大変ありがたいです。
何か不明点あれば、ご質問いただけますと幸いです。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

y_waiwai

2021/12/17 09:00

連続で出力するとは?どうなればいいんでしょう
dodox86

2021/12/17 09:23 編集

0.1秒間保持、と言うのが謎です。送信はワンショット、1回送ったら終わりです。
jimbe

2021/12/17 10:02

> 無限ループで出力してみましたが、タブレットの画面は真っ白 android はシングルタスクですから、単純に無限ループすれば表示も操作も一切動作しなくなるのは "当然" です。 > どうにか実現したい事ができないかインターネットで調べてみました どのような調査をされたのでしょうか。
roatt

2021/12/17 14:46

>連続で出力するとは?どうなればいいんでしょう >0.1秒間保持、と言うのが謎です。送信はワンショット、1回送ったら終わりです。 連続で送信されるといったほうが正しいかもしれません。 何もボタンを押さなければ ずっと「1 1 1 1 1 1……」と送信され、 Button1が押されれば0.1秒間「A A A A ……」と送信されるようなイメージです。 その後何もボタンが押されなければまた「1 1 1 1……」と送信されますし、Button2が押されれば0.1秒間「B B B B……」文字列が送信されるプログラムを作りたいです。
jimbe

2021/12/17 15:11

> android はシングルタスクですから、単純に無限ループすれば すいません、 AsyncTask の doInBackground 内であれば関係無いですね。 失礼しました。
roatt

2021/12/17 15:19 編集

>すいません、 AsyncTask の doInBackground 内であれば関係無いですね。 >失礼しました。 いえいえ、いろいろ教えていただき感謝しています。 >どのような調査をされたのでしょうか。 連続で送信される方法を探したのですが、文字列を1度送信する方法しか見つけられませんでした。 指定時間分処理を行うプログラムも探しました。 Timer関数を使うものは発見しましたがいまいち理解できずやりたいことと同じことができるのかわからずこちらに質問をさせていただいた次第です。
guest

回答1

0

ベストアンサー

絶え間なく送り続けるのも怖いので、 10ms 毎に送るものにしてみました。
と言っても IP はホストPCを指定しているものの、受信側を作るのが面倒だったので ( コメントにしてある )Log のみで確認しています。

java

1package com.teratail.q374315; 2 3import androidx.appcompat.app.AppCompatActivity; 4 5import android.os.*; 6import android.util.Log; 7import android.widget.Button; 8 9import java.io.IOException; 10import java.net.*; 11import java.util.*; 12 13public class MainActivity extends AppCompatActivity { 14 private static final String HOSTNAME = "10.0.2.2"; //エミュレータ→ホストPC 15 private static final int PORT = 34567; //ポート宣言 16 private static final String[] DATAS = {"1","A","B","C"}; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 try { 24 DatagramTimerTask timerTask = new DatagramTimerTask(InetAddress.getByName(HOSTNAME), PORT, DATAS); 25 26 Button button1 = findViewById(R.id.button1); 27 button1.setOnClickListener(view -> timerTask.changePacket(1)); 28 29 Button button2 = findViewById(R.id.button2); 30 button2.setOnClickListener(view -> timerTask.changePacket(2)); 31 32 Button button3 = findViewById(R.id.button3); 33 button3.setOnClickListener(view -> timerTask.changePacket(3)); 34 35 Timer timer = new Timer(); 36 timer.scheduleAtFixedRate(timerTask, 0, 10); //[ms] 37 38 } catch(SocketException e) { 39 e.printStackTrace(); 40 } catch(UnknownHostException e) { 41 e.printStackTrace(); 42 } 43 } 44} 45 46class DatagramTimerTask extends TimerTask { 47 private DatagramSocket soc; 48 private DatagramPacket[] packets; 49 private int packetIndex; 50 private int count; 51 52 DatagramTimerTask(InetAddress host, int port, String[] datas) throws SocketException { 53 soc = new DatagramSocket(); //送るだけなら bind は不要 54 55 packets = new DatagramPacket[datas.length]; 56 for(int i=0; i<datas.length; i++) { 57 byte[] buf = datas[i].getBytes(); 58 packets[i] = new DatagramPacket(buf, buf.length, host, port); 59 } 60 } 61 62 @Override 63 public void run() { 64 if(soc == null) return; 65 try { 66 synchronized(this) { 67 soc.send(packets[packetIndex]); 68 //Log.d("DatagramTimerTask","Datagram send index="+packetIndex); 69 if(count>0 && --count==0) packetIndex = 0; 70 } 71 } catch(IOException e) { 72 e.printStackTrace(); 73 soc.close(); 74 soc = null; 75 } 76 } 77 78 public void changePacket(int packetIndex) { 79 if(packetIndex < 0 || packetIndex >= packets.length) throw new IllegalArgumentException(); 80 synchronized(this) { 81 this.packetIndex = packetIndex; 82 this.count = 10; //10ms 毎に 10 回実行されれば 0.1 秒 83 } 84 } 85}

投稿2021/12/17 16:28

jimbe

総合スコア12648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

roatt

2021/12/20 02:04

返信が遅れ、申し訳ございません。 コードまでいただきありがとうございます。 一度試し、不明な関数は一度調べて理解を深めたいと思います。 その際、調べても理解できないところがありましたら質問させてください… (自分なりに理解できたところでベストアンサーさせていただければ幸いです。)
roatt

2021/12/20 06:45

jimbe様 いただいたコードで実行したところ、まさに私が実装したかったものになっておりました。 本当にありがとうございます。 知らないコードを調べ、確認したいところ、わからないところがあったため、以下の質問にお答えいただければ幸いです。 ・このプログラムの流れとしては、  ①DatagramTimerTaskクラスでソケットの構築とpackets[i]に各文字コードをbyte型に変換したものを格納しておく  ②public void run()内のものをtimer.scheduleAtFixedRateで10ms単位で繰り返す  ※各ボタン押下時、changePacketが呼び出され、packetIndexの数値が変更され、serverSocket.send(packets[packetIndex]);で該当の文字コードが送信される。  というようなイメージで合っておりますでしょうか? ・packets = new DatagramPacket[datas.length];が何を行っているのかをご教示いただきたいです。  文字コードの文字数文の配列と読み解き、文字数もすべて1文字のため、数値に置き換えるとpackets = new DatagramPacket[1];なのかなと考えましたが、イマイチ腑に落ちておりません。 ・ソケットのクローズ後、serverSocket = null;にするのはなぜでしょうか? ・if(packetIndex < 0 || packetIndex >= packets.length) throw new IllegalArgumentException();が何を意味しているのかわかりませんでした。  IllegalArgumentExceptionが「メソッドに違法または不適切な引数が渡されたことを示す」とありますが、よくわかりません…  packetIndexは「0,1,2,3」なので、マイナスになることもないかと思われますが、まかり間違ってマイナスが出てしまった際の例外処理というものなのでしょうか?  また、packetIndex >= packets.lengthの条件もなぜついているのか理由をお聞きしたいです。 たくさん質問をして、申し訳ありません。 お手数ですが、よろしくお願いいたします。
jimbe

2021/12/20 08:14

> ①DatagramTimerTaskクラスでソケットの構築とpackets[i]に各文字コードをbyte型に変換したものを格納しておく この後で引用されています通り、該当行は > packets = new DatagramPacket[datas.length]; であり、 packets の宣言は > private DatagramPacket[] packets; ですので、packets 配列の各要素は「各文字コードをbyte型に変換したもの」ではありません。 送信するデータグラムパケットそのものです。 > 数値に置き換えるとpackets = new DatagramPacket[1];なのかなと datas は DatagramTimerTask のコンストラクトパラメータで、 その元は > private static final String[] DATAS = {"1","A","B","C"}; です。ですから new DatagramPacket[datas.length] は new DatagramPacket[4] ということになります。 「packets 配列は 4 つの DatagramPacket オブジェクトを保持する領域を持つ」ということです。 > serverSocket = null; そのような行はありません。run() 内の 例外発生時の soc = null のことでしょうか。 回答のコードは基本的には参考ですので、ソケットの後始末まではキチンとはしていません。 タイマーの停止もしていませんので、もし送信で何か異常が発生した場合でも延々と送信をしようとし、延々と例外が発生することになります。 異常が起きた場合はログに例外のメッセージを出力しますが、延々と(恐らくは同じ)例外のメッセージが残っていても意味がありませんので、例外発生時は一応 close した上で soc を null とし、 run() の先頭で null だったら送信を行わないことで、メッセージが延々とログに残るのを防いでいます。 この辺りの処理内容につきましては、「送信で異常が起きたらどうするか」という設計によって異なります。 「何か画面に表示して、通信が出来るようになるまでタイマーを停止する」とかであれば、そのようなコードを加える必要があるでしょう。 > if(packetIndex < 0 || packetIndex >= packets.length) throw new IllegalArgumentException();が何を意味しているのか 単なるパラメータチェックです。 「参考コードなら別に無くても」良いんですが、このコードでは packetIndex の値は changePacket() 内では無く run() 内で使用しています。「値を受け取る個所」と「実際に使う個所」が動作タイミングとして離れていると分かり難いですので、受け取る個所の changePacket() で早々にエラーとしています。 > packetIndexは「0,1,2,3」なので と仰られる通り「 packetIndex が 0 未満か 4 以上ならパラメータエラー(例外)」という文です。
jimbe

2021/12/20 08:47 編集

システムを構成するプログラムは、正常動作時の処理だけではありません。 今はどうなのか知りませんが、以前チラと見聞きした話では、システムを構成するプログラムの 7~80% は異常が起きた時に対処する処理だそうです。 数値の真偽はともかく、プログラムが動作する環境は常に変わり、それに対してどこまで堅牢に動作し続けることが出来るかが、プログラムの質の評価に関わります。 それはユーザが業務に使う場合に限らず、自分以外のプログラマ(時には"明日の自分")が開発に使う場合も含まれます。 パラメータが範囲内にある必要があり、範囲外を指定したら何が起きるのか、通信できていたものが突然できなくなったら何が起きるのか、そしてそれぞれ何をするべきなのかは、常に考える必要があります。(考えた結果「何もしない」という判断ももちろんあり得ます。)
roatt

2021/12/20 09:18 編集

なるほど、非常にわかりやすい説明をありがとうございます。 ※おっしゃる通りsoc = nullのことを質問したかったのですが、別のプログラムのものを引用してしまいました…  申し訳ありません。 追加で質問なのですが、いただいたコードのところにコメントで「送るだけならbindは不要」とはどのような意味でしょうか? UdpIpToolにて受信側を見たところ、接続切断はポート番号(34567)で文字コードの送信は別の番号(そのときに接続できる番号?)でした。 何度か接続切断を繰り返したときに、一瞬だけ別のポート番号で受信され、例えばbutton1を押した場合、「AAA11A1AAA」みたいな感じで受信されていることがあります。 それを阻止するために、ポート番号の送信側のbindが必要なのではないかと考えたのですが、いかがでしょうか? (安易に 「soc = new DatagramSocket(34567);  soc.bind(new InetSocketAddress(PORT)); 」 と入れてみましたが、当たり前ですが、うまく送受信はできませんでした。 設定したポートで送受信を行うとした場合、jimbe様がおっしゃっていた「面倒な受信側の作成」を行わなければならないのでしょうか?)
jimbe

2021/12/20 09:56 編集

IP 通信はアドレスとポート番号の組で行われます。 UDP にせよ TCP にせよ、受信側は特定のアドレス・ポートで受信待ちをし、送信側はそこに送ります。 ですから、受信側はポートのバインド(してそのポートを送信側に予め伝えておくこと)が必要ですが、送信側はどのポートを使っても問題ありません。DatagramSocket はポート番号が指定されないと、テキトウに空いているポートを使います。 現実の手紙で言えば、受け取り側の居る住所さえ分かっていれば、送る側は何処にいても(投函さえ出来れば)送れるのと同じことです。 受信側で受信するパケットの順が送信側が送信した順と異なることがあるのは UDP の仕様です。 順番に届くこと(もしくは順番に届いたように見せること)が必要な場合は、そのようなプロトコルを作成する必要があります。 UDP が何を保証して何を保証していないかは、 UDP の仕様を御覧ください。
jimbe

2021/12/20 10:03

私がテストの為に受信側を作るのを面倒としましたのは、送信側が Android で 受信側が PC ですと、 AndroidStudio と Eclipse を同時に動かしてそれぞれにコードを入力してエミュレータも動かして(他に資料を探すブラウザやら BGM 的な YouTube やらゲームやら)…とディスプレイ内がごちゃごちゃして切り替えながら動かすのが「面倒」だったのです ^^;;;
jimbe

2021/12/20 10:36 編集

念を押すようですが、回答のコードで送信に使う DatagramSocket に指定するのは、「受信側がどのポートで待っているか」では無く、送信にどのポートを使うか、です。 「受信側がどのポートで待っているか」は DatagramPacket のコンストラクタで ( アドレスと共に ) 指定しており、プロトコル上は両者のポート番号に関係はありません。
roatt

2021/12/20 12:55

繰り返しになってしまうかもしれませんが、今回のコードに置き換えると 送信側はDatagramSocketでポートを指定せず、そのとき空いているポートから文字コードを送信していて、 受信側はnew DatagramPacket(buf, buf.length, host, port);でhostとportから受信側の住所(今回で言うHOSTNAME = "10.0.2.2"とPORT = 34567)を指定している という理解であっていますか?
jimbe

2021/12/21 04:06 編集

UDP の送受信の単位は"文字コード"では無く"UDPパケット"です。 その"UDPパケット"を java の オブジェクトとして表したものが DatagramPacket オブジェクト、そのパケットを送受信する機能を有するのが DatagramSocket オブジェクトです。 仰る言葉を置き換えれば、 「送信は (DatagramSocket でポートを指定しないことで ) そのとき空いているポートからパケットを送信していて、その時の送り先(受信側)は new DatagramPacket(buf, buf.length, host, port) の host と port ( 今回で言う HOSTNAME="10.0.2.2" と PORT=34567) で指定している」 という感じでしょうか。 buf( と buf.length) がパケットの中身=手紙の本文ですから、new DatagramPacket(buf, buf.length, host, port) で「ハガキの表の住所宛名と裏の本文全てを書き込んだ」ようなパケットの生成となっています。
roatt

2021/12/21 02:33

非常にわかりやすい説明をありがとうございました。 納得しました! 私のつたない質問に根気よく説明いただき本当に感謝しております。 いつもありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問