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

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

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

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Q&A

0回答

463閲覧

複数端末への一斉送信を実現するsocket通信のやり方(android)

Mustard

総合スコア11

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

0グッド

1クリップ

投稿2019/05/05 09:44

編集2019/05/10 15:00

現在socket通信の勉強としてWiFiDirectServiceDiscoveryというサンプルを調べています。
このデモプログラムはwifi-directを用いたp2p通信でのチャットアプリとなっており、実機での動作確認をしたところ1対1のチャットは正常に実行できました。

本題です。今、端末A,B,Cと3つの端末があるとします。端末Aと端末Bが通信を構築した後に、グループオーナーとなった端末(ここでは端末Aとします)に対して端末Cが接続要求を送信したところ端末Aには承認の是非を問うダイアログが表示され、承認すると端末A,C間で通信が構築されました。
このとき、端末B,Cから送信したメッセージは端末Aに受信されることを確認しています。
一方で端末Aからのメッセージは後から接続された端末Cにのみ受信されていました。
このデモプログラムを書き換えることで端末Aからのメッセージを端末B,Cの両方に受信させるにはどのようにすればよいでしょうか。

デバッグのログにはBindException Address Already in Use Androidとあったので、接続ごとに異なるポートやスレッドを用意する必要があるのかなと考えたのですが、お恥ずかしながら知識不足のため実装の検討がつきません。
大変恐縮ではございますが実装案等のご助言をお願い致します。

参考までにデモプログラムでのソケット通信に関係のありそうな部分を以下に示します。

GroupOwnerSocketHandler

1public class GroupOwnerSocketHandler extends Thread { 2 3 ServerSocket socket = null; 4 private final int THREAD_COUNT = 10; 5 private Handler handler; 6 private static final String TAG = "GroupOwnerSocketHandler"; 7 8 public GroupOwnerSocketHandler(Handler handler) throws IOException { 9 try { 10 socket = new ServerSocket(4545); 11 this.handler = handler; 12 Log.d("GroupOwnerSocketHandler", "Socket Started"); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 pool.shutdownNow(); 16 throw e; 17 } 18 19 } 20 21 /** 22 * A ThreadPool for client sockets. 23 */ 24 private final ThreadPoolExecutor pool = new ThreadPoolExecutor( 25 THREAD_COUNT, THREAD_COUNT, 10, TimeUnit.SECONDS, 26 new LinkedBlockingQueue<Runnable>()); 27 28 @Override 29 public void run() { 30 while (true) { 31 try { 32 // A blocking operation. Initiate a ChatManager instance when 33 // there is a new connection 34 pool.execute(new ChatManager(socket.accept(), handler)); 35 Log.d(TAG, "Launching the I/O handler"); 36 37 } catch (IOException e) { 38 try { 39 if (socket != null && !socket.isClosed()) 40 socket.close(); 41 } catch (IOException ioe) { 42 43 } 44 e.printStackTrace(); 45 pool.shutdownNow(); 46 break; 47 } 48 } 49 } 50 51}

ClientSocketHandler

1public class ClientSocketHandler extends Thread { 2 3 private static final String TAG = "ClientSocketHandler"; 4 private Handler handler; 5 private ChatManager chat; 6 private InetAddress mAddress; 7 8 public ClientSocketHandler(Handler handler, InetAddress groupOwnerAddress) { 9 this.handler = handler; 10 this.mAddress = groupOwnerAddress; 11 } 12 13 @Override 14 public void run() { 15 Socket socket = new Socket(); 16 try { 17 socket.bind(null); 18 socket.connect(new InetSocketAddress(mAddress.getHostAddress(), 19 WiFiServiceDiscoveryActivity.SERVER_PORT), 5000); 20 Log.d(TAG, "Launching the I/O handler"); 21 chat = new ChatManager(socket, handler); 22 new Thread(chat).start(); 23 } catch (IOException e) { 24 e.printStackTrace(); 25 try { 26 socket.close(); 27 } catch (IOException e1) { 28 e1.printStackTrace(); 29 } 30 return; 31 } 32 } 33 34 public ChatManager getChat() { 35 return chat; 36 } 37 38}

ChatManager

1public class ChatManager implements Runnable { 2 3 private Socket socket = null; 4 private Handler handler; 5 6 public ChatManager(Socket socket, Handler handler) { 7 this.socket = socket; 8 this.handler = handler; 9 } 10 11 private InputStream iStream; 12 private OutputStream oStream; 13 private static final String TAG = "ChatHandler"; 14 15 @Override 16 public void run() { 17 try { 18 19 iStream = socket.getInputStream(); 20 oStream = socket.getOutputStream(); 21 byte[] buffer = new byte[1024]; 22 int bytes; 23 handler.obtainMessage(WiFiServiceDiscoveryActivity.MY_HANDLE, this) 24 .sendToTarget(); 25 26 while (true) { 27 try { 28 // Read from the InputStream 29 bytes = iStream.read(buffer); 30 if (bytes == -1) { 31 break; 32 } 33 34 // Send the obtained bytes to the UI Activity 35 Log.d(TAG, "Rec:" + String.valueOf(buffer)); 36 handler.obtainMessage(WiFiServiceDiscoveryActivity.MESSAGE_READ, 37 bytes, -1, buffer).sendToTarget(); 38 } catch (IOException e) { 39 Log.e(TAG, "disconnected", e); 40 } 41 } 42 } catch (IOException e) { 43 e.printStackTrace(); 44 } finally { 45 try { 46 socket.close(); 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } 50 } 51 } 52 53 public void write(byte[] buffer) { 54 try { 55 oStream.write(buffer); 56 } catch (IOException e) { 57 Log.e(TAG, "Exception during write", e); 58 } 59 } 60 61}

(追記)
socketをクライアント端末ごとに用意すればいいと考えGroupOwnerSocketHandler内でArrayList<Socket> socketListを作成し、リスト内のすべてのソケットに対してwrite処理をすればよいと考えたのですがどうでしょうか。

GroupOwnerSocketHandler

1public class GroupOwnerSocketHandler extends Thread { 2public static ArrayList<Socket> socketList = new ArrayList<Socket>(); 3--(略)-- 4 @Override 5 public void run() { 6 while (true) { 7 try { 8 //クライアントごとにsocketをリストで管理 9 Socket clientSocket = socket.accept(); 10 socketList.add(clientSocket); 11 pool.execute(new ChatManager(clientSocket, handler)); 12 Log.d(TAG, "Launching the I/O handler"); 13 14 } catch (IOException e) { 15 --(略)-- 16 } 17 } 18 } 19 20}

ChatManager

1public class ChatManager implements Runnable { 2--(略)-- 3 public void write(byte[] buffer) { 4 //ソケットの数だけwrite 5 for(Iterator<Socket> it = GroupOwnerSocketHandler.socketList.iterator(); it.hasNext();) { 6 Socket s = it.next(); 7 try { 8 OutputStream os = s.getOutputStream(); 9 os.write(buffer); 10 } catch (IOException e) { 11 Log.e(TAG, "Exception during write", e); 12 } 13 } 14 } 15}

このように書き換えたところ、socketListには確かに端末B,Cそれぞれのsocketが別のポートの値で格納されていましたが受信できたのは先に接続した端末Bだけでした。ループ処理も二回実行されていたためwrite自体は2回呼ばれているはずなのですが...

(追記2)

GroupOwnerSocketHandler

1public class GroupOwnerSocketHandler extends Thread { 2public static ArrayList<ChatManager> chatList = new ArrayList<ChatManager>(); 3--(略)-- 4 @Override 5 public void run() { 6 while (true) { 7 try { 8 //クライアントごとにsocketをリストで管理 9 Socket clientSocket = socket.accept(); 10 ChatManager clientChat = new ChatManager(clientSocket, handler); 11 chatList.add(clientChat); 12 pool.execute(clientChat); 13 Log.d(TAG, "Launching the I/O handler"); 14 } catch (IOException e) { 15 --(略)-- 16 } 17 } 18 } 19}

とすることで複数の端末に送信することができました。
ですが今度は同じ端末に対して一度しか送信がされなくなってしまいました。(端末AからB,Cに対して一度ずつ送信を確認)
ChatManagerを配列として管理したので更新が正常に行われていないのではないかと考えています。
ChatManagerからhandler.obtainMessageを受け取った際のアクティビティの処理は以下のようになっていました。

WiFiServiceDiscoveryActivity

1 @Override 2 public boolean handleMessage(Message msg) { 3 switch (msg.what) { 4 case MESSAGE_READ: 5 byte[] readBuf = (byte[]) msg.obj; 6 // construct a string from the valid bytes in the buffer 7 String readMessage = new String(readBuf, 0, msg.arg1); 8 Log.d(TAG, readMessage); 9 (chatFragment).pushMessage("Buddy: " + readMessage); 10 break; 11 12 case MY_HANDLE: 13 Object obj = msg.obj; 14 (chatFragment).setChatManager((ChatManager) obj); 15 16 } 17 return true; 18 }

setChatManager

1public void setChatManager(ChatManager obj) { 2 chatManager = obj; 3 }

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

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

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

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

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

jimbe

2019/05/06 09:39

まず, 構造が変になってしまっていませんでしょうか. ChatManager はクライアント毎に生成されていますが, write メソッドは他の ChatManager が対応するはずの Socket まで GroupOwnerSocketHandler.socketList を通じて使ってしまっています. Socket の配列では無く ChatManager の配列で各 write メソッドを呼ぶべきかと思います. また, ソケットの入出力は Stream ですが, Stream は大抵はバッファがあり, Output においては write した全てがすぐ送られるとは限りません. close() か flash() によってバッファから全て吐き出されますので, write の後は flash を呼ぶようにしたほうが確実かと思います. Input においては, read が buffer に格納したデータ量は返り値で示されますので, (先の画像の場合もそうでしたが) 送られてくるデータ量を把握して全て受信してから handler を呼んだほうが良いのではないでしょうか.
Mustard

2019/05/10 07:28 編集

おっしゃる通りChatManagerを配列に格納したところ複数通信が実装できました。本当にありがとうございます。 ところが今度は同じ端末に複数回送信することができなくなってしまいまして... ChatManagerからメッセージを受け取った際アクティビティ側でsetChatManagerが呼び出されChatManagerが貼り直されると解釈しているのですが public void setChatManager(ChatManager obj) { chatManager = obj; } を public void setChatManager(int position,ChatManager obj) { GroupOwnerSocketHandler.chatList.set(position,obj); } のように書き換えても再度同じ端末と通信を行うことができませんでした。 なにか見落としがあるのでしょうか。 (追記) 文字の場合同じ端末とも複数回通信できることを確認しました。 画像の場合初回の画像を受信して以降の受信ができません...
jimbe

2019/05/12 09:54

画像と文字列を同じストリームを使って送信しているのでしょうか. その場合, 受信する側は文字列なのか画像なのかの判断はどのようにされていますか.
kazoooooo

2021/01/07 02:12

失礼します。 自分も複数端末にデータを送信しようとしているのですがこちらは解決したでしょうか?していたらぜひ教えていただきたいです。 よろしくお願いします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問