JavaのNew IO(nio)のisWritableがtrueになる条件がわかりません。

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,167

inca1987

score 18

 概要

java.nioを用いてAndroidで動作するTCPクライアントを作成しています。
nioのセレクタ、チャンネルの仕組みについて理解できない部分があったため質問致しました。

 質問

下記ソースにおける、key.isWritableはどのような場合にtrueになるのでしょうか。

ドキュメントも見たのですが、
>対応するチャネルが「書き込み可能な状態」になっているか、次の書き込みができるようにリモートシャットダウンされているか、エラーによって一時停止していることを検出する
となっており、恥ずかしながらどういった状態が「書き込み可能な状態」であるのか理解できませんでした。

よろしくお願いいたします。

 ソース

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.Executors;

public class TCPClientForUnity{

    private SocketChannel mSocketChannel;
    private Selector mSelector;
    private ByteBuffer mReadBuffer;
    private ByteBuffer mWriteBuffer;
    private String mAddress;
    private int mPort;

    public TCPClientForUnity(final String adr, final int PORT){
        mAddress = adr;
        mPort = PORT;
    }

    public void CreateBuffer(final int size){
        mReadBuffer = ByteBuffer.allocate(size);
        mWriteBuffer = ByteBuffer.allocate(size);
    }

    public void CreateSocketChannel(final String adr, final int PORT){
        try {
            mSelector = Selector.open();
            mSocketChannel = SocketChannel.open();
            mSocketChannel.configureBlocking(false);
            mSocketChannel.connect(new InetSocketAddress(adr, PORT));
            mSocketChannel.register(mSelector, SelectionKey.OP_CONNECT);
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    private final Runnable mAcceptTask = new Runnable() {
        public void run() {
            System.out.println("start runnnable");
            CreateBuffer(1024);

            try { //IOException

                while (!Thread.interrupted()) {
                    try { //ConnectException

                        CreateSocketChannel(mAddress, mPort);
                        while (!Thread.interrupted() & mSocketChannel.isOpen()) {
                            mSelector.select();
                            Iterator<SelectionKey> keys = mSelector.selectedKeys().iterator();

                            while (keys.hasNext()) {
                                SelectionKey key = keys.next();
                                keys.remove();

                                if (key.isConnectable()) {
                                    System.out.println("connecting...");
                                    mSocketChannel.finishConnect(); // if connection fail, throw ConnectException
                                    System.out.println("CLIENT: Connected");
                                    mSocketChannel.register(mSelector, SelectionKey.OP_WRITE, mWriteBuffer);
                                    mSocketChannel.register(mSelector, SelectionKey.OP_READ, mReadBuffer);
                                }

                                if (key.isWritable()) {
                                    ByteBuffer writeBuf = (ByteBuffer) key.attachment();
                                    mSocketChannel.write(writeBuf);
                                    if (!mWriteBuffer.hasRemaining()) {
                                        System.out.println("CLIENT: Write " + Arrays.toString(mWriteBuffer.array()));
                                    }
                                }

                                if (key.isReadable()) {
                                    ByteBuffer readBuf = (ByteBuffer) key.attachment();
                                    mSocketChannel.read(readBuf);
                                    if (!readBuf.hasRemaining()) {
                                        String msg = Arrays.toString(readBuf.array());
                                        System.out.println("CLIENT: Read  " + msg);
                                        SendToRaspi(msg);
                                    }
                                    //mSocketChannel.close();
                                    //break loop;
                                }
                            }
                        }
                    } catch (ConnectException e) {
                        e.getStackTrace();
                        try{
                            Thread.sleep(100);
                        }catch (InterruptedException  e2){
                            e2.getStackTrace();
                        }
                    }
                }
            }catch (IOException e) {

            }
        }
    };

    public void SendToUnity(String msg){
        System.out.println("in TCPClientForUnity, msg: " + msg);
        mWriteBuffer.put(msg.getBytes(Charset.forName("UTF-8")));
    }

    public void SendToRaspi(String msg){
        System.out.println("plz over write");
    }

    public void start() {
        Executors.newSingleThreadExecutor().execute(mAcceptTask);
    }

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

+1

メインの質問については自己解決致しました。
registerの機能を理解できていなかった。というのが原因の全てです。

isWritableをtrueにするには、
毎回register(mSelector, SelectionKey.OP_WRITE)呼び出す必要があるようで、
自身のコードで言うと、SendToUnityを下記のように変更する必要がありました。

public void SendToUnity(String msg){
        mWriteBuffer.clear();
        mWriteBuffer.put(msg.getBytes(Charset.forName("UTF-8")));

        try{
            mSocketChannel.register(mSelector, SelectionKey.OP_WRITE);
        }catch (IOException e){
            e.getStackTrace();
        }
    }

register時にbufferを登録していたので、bufferを監視してisWritableをtrueにするんじゃないか?
と思い込んでしまったのですが、アレはただ用いるバッファを添付しているに過ぎないようです。
お目汚し失礼いたしました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/09/19 17:50

    お役には立てませんでしたが解決されたようでなによりです。

    キャンセル

0

質問に対する答えではないのですが・・・

コードを見る限り単一の接続に対するsend/receiveをしたいというのが目的に見えます。もしそうならnioを使うのはオーバースペックでは?サーバーを実装する際など複数の平行して発生するI/O(通信やファイルI/Oなど)の多重度などを制御したい場合はnioが必要になりますが、サーバーへの単一接続に対するI/O処理ならjava.net.Socketを利用する方がシンプルですのでそれを使うのが一般的と思います。(ただしSocketを使って接続したりsend/receiveする際に呼び出し元スレッドがブロックするのでUIスレッドとは別スレッドで通信することにはなります)

質問者さんが既にSocketでは問題ありと判断されてnioを調べておられるのでしたらすみませんがこのコメントはスルーしてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/09/16 21:16

    回答ありがとうございます。
    Socketやノンブロッキング処理に対する知識が浅いので、是非ご教授いただけると幸いです。

    通信はlocalhost内で行い、「AndroidNative」↔「Android上で動作するUnity」間の双方向通信を、同時並行で遅延を可能な限り少なく実現したいと考えております。
    例えば、
    1.Unity上でオブジェクトをタッチした情報をAndroidNativeへ送信
    (2.AndroidNativeを経由してセンサを積んだマイコン上に送信)
    (3.センサの状況を加味して決定したUnity上のキャラクターのモーションをAndroidNativeに送信)
    4.AndroidNativeからUnityに転送
    という処理の流れを、他の通信と同時並行でなるべく遅延なく実現できる方法が必要です。

    送受信データについては、たかだかサイズ1kB未満の文字列ですが、同時並行での送受信時ブロックが生じた際の遅延がどの程度になるか見積もることができなかったので、ノンブロッキング処理を行えるnioを選択しました。
    この状況においても、やはりnioはオーバースペックでしょうか。ぜひご意見を伺いたいです。

    追伸:
    実は、以前の質問(https://teratail.com/questions/46856)で書いたUnity-AndroidNative間通信の問題が解消できず、行き詰まってしまったがための代替手段ですので、TCP通信を用いるのは少し不自然な場面かもしれません。

    キャンセル

  • 2016/09/16 22:54

    Unityの仕様とAndroidNativeでやろうとしている処理がどういったものかわからないので残念ながら自分には現状の情報だけではお答えできません。UnityはゲームエンジンでAndroidNativeは要するにJNIを使ってハードウェアの機械語で直接動作するnativeコードとやりとりできるという理解であっているでしょうか。現状の情報では以下のような疑問が次々にわいてくるのでお答えできないのです。

    1. UnityはAndroidのUIスレッド上で動作するものですか?それとも別のスレッドで実行されているものですか?
    2. Unity上のオブジェクトをタッチした再、UIスレッドでハンドラーが実行されるのですか?それともUnity独自のスレッドでハンドラーが呼ばれるのですか?それとも色々な方法がある?
    3. nativeコードはスレッドセーフに作られていますか?それとも単一のスレッドからのみ実行することが必要ですか?
    4. センサーの状況を加味してUnityのキャラクターのモーションを決める処理はJavaで行う?nativeコードで行う?Javaで行うならセンサーの検出値をnativeコードを呼び出してJava側へ戻すことになりますしnativeコード側でやるならUnityのキャラクターの情報をnativeコードへいつ伝えるのかといった方針も関係してくると思います。

    キャンセル

  • 2016/09/17 00:08

    詳細なご指摘ありがとうございます。

    自分にはKSwordOfHasteさんの質問に正確に答えるだけの知識が無いものと思われますが、
    出来る限り調査したうえで、答えさせていただきます。

    まずは「Unity」と「AndroidNative」という表現ですが、
    「Unity」は「Unityプロジェクト上のコードおよびビルドされたAndroid上で実行可能なバイナリ」の意で、
    「AndroidNative」は「上記以外のAndroidコードおよびビルドされたバイナリ」の意で言っていました。
    勝手な表現で誤解を招いたことをお詫びいたします。

    また明日調査した上で、ご指摘1〜4への回答をさせていただきたいと思います。

    キャンセル

  • 2016/09/19 13:55

    Android及びJavaへの理解が浅い自分にとって、KSwordOfHasteさんの質問に正確に答える事は少し荷が重いようです。
    大変申し訳ございません。ありがとうございました。

    キャンセル

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

  • ただいまの回答率 90.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる