3

クライアント、携帯電話からデータを受信するBluetoothサーバーがあります。私が使用しているコードは次のようになります

@Override
public void run() {
    try {
        this.localDevice = LocalDevice.getLocalDevice();
        this.localDevice.setDiscoverable(DiscoveryAgent.GIAC);

        this.server = (StreamConnectionNotifier) Connector.open(URL);

        while(true) {
            if(this.connection == null) {
                this.connection = this.server.acceptAndOpen();

                System.out.println("INFO: Bluetooth client connected");

                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.openInputStream()));
                this.writer = new BufferedWriter(new OutputStreamWriter(connection.openOutputStream()));

                String line;
                while((line = reader.readLine()) != null) {
                    if(line.equals("--#do:disconnect")) {
                        break;
                    }

                    System.out.println("INFO: Received from Bluetooth: " + line);
                }

                System.out.println("INFO: Client disconnected");
            }
        }
    } catch(BluetoothStateException ex) {
        ex.printStackTrace();
    } catch(IOException ex) {
        ex.printStackTrace();
    }
}

ご覧のとおり、停止するように指示されるまでメッセージを受信する無限ループがあります。現時点では、ループはすべてのメッセージを受信して​​います。それには問題があります。コードが使用されるクラスは、MVC のモデル クラスです。クラスには、 というメソッドもありますgetContacts()。Bluetooth経由で電話から連絡先を受信するために使用されます。サーバーが を送信すると、電話機は連絡先を送信するように指示されます--#do:getcontacts

私がする必要があるのは、メソッドの ArrayList で連絡先を取得し、getContacts()それをメソッドの戻り値として返して、コントローラーが連絡先を処理できるようにすることです。

public ArrayList<Contact> getContacts() {
    ArrayList<Contact> contacts = new ArrayList<>();

    // How do I get the contacts in the ArrayList?

    return contacts;
}
4

2 に答える 2

2

まず第一に、レビュー/修正が必要なコードはほとんどありません

1-ArrayList<Contact> contactsクラスで定義する必要があるため、スレッドはそれにアクセスして、メソッド内のローカル変数としてではなく設定できますgetContacts()

public ArrayList<Contact> getContacts() {

    //ArrayList<Contact> contacts = new ArrayList<>();    

    return contacts;
}

2- 必要なときにスレッドを停止できるように、run メソッド内で無限ループを使用しないようにする必要があります。

//while(true)
while(isRunning) { // a flag that is set to true by default

}

3-切断後にnullに設定せずに接続が等しいことを確認すると、接続は最初のクライアントからのみ受け入れられ(接続が最初にnullに設定されていると仮定)、その後は無限ループが発生しますが、コードはそうでthis.connection = this.server.acceptAndOpen();はありませんもう到達可能

if(this.connection == null) {               
  while((line = reader.readLine()) != null) {
     if(line.equals("--#do:disconnect")) {
           // You have to set it to null if you want to continue listening after disconnecting
           this.connection = null
           break;
      }
   }
 }

または、このチェックを完全に削除するだけです。役に立たないことがわかります。

質問に戻ります。

run()getContacts()メソッドの両方からアクセスできるように、連絡先リストをクラス メンバーとして定義できます。必要に応じて、最終的にすることができます。次に、このリストをrun()メソッド内に入力します。それで全部です。

例えば

 class MyServerThread implements Runnable {
    private boolean isRunning = true;
    ArrayList<Contact> contacts = new ArrayList<>();

    public ArrayList<Contact> getContacts(){
        // Make sure that your not currently updating the contacts when this method is called
        // you can define a boolean flag and/or use synchronization
        return contacts;
    }

    public void run() {

     ...

    while(isRunning ) {
            this.connection = this.server.acceptAndOpen();

            System.out.println("INFO: Bluetooth client connected");

            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.openInputStream()));
            this.writer = new BufferedWriter(new OutputStreamWriter(connection.openOutputStream()));

            // You need to remove previously received contacts before issuing a new --#do:getcontacts command                
            contacts.clear();

            String line;
            while((line = reader.readLine()) != null) {
                if(line.equals("--#do:disconnect")) {
                    break;
                }

                // Here you can parse the contact information
                String contactName = ...
                String contactPhone = ...
                contacts.add(new Contact(contactName,contactPhone));
            }

            System.out.println("INFO: Client disconnected");            
    }
} catch(BluetoothStateException ex) {
    ex.printStackTrace();
} catch(IOException ex) {
    ex.printStackTrace();
}
  }

 }

オブジェクトのシリアル化を使用する必要はありません。電話から PC に連絡先を送信するための簡単なプロトコルを作成できます。たとえば、送信するコマンドに似たものです。--#C:name$phone

于 2013-02-09T02:04:23.457 に答える
2

いくつかの提案があります。私の例は実用的なコードではなく、あなたのための作業ベースにすぎません。

まず、サーバーでスレッドを使用することを強くお勧めします。クライアントがサーバーに接続するたびに、スレッドを開始するために必要なすべてのデータを含むパラメーターを使用して、新しいスレッドを作成します。

boolean running = true;    //this class variable will allow you to shut down the server correctly

public void stopServer(){    //this method will shut down the server
    this.running = false;
}

public void run() {
    ...

    while(running) {
        // if(this.connection == null) {  // I removed this line since it's unnecessary, or even harmful!
        StreamConnection connection = this.server.acceptAndOpen();  //This line will block until a connection is made...
        System.out.println("INFO: Bluetooth client connected");
        Thread thread = new ServerThread(connection);
        thread.start()              //don't forget exception handling...
    } 
}

クラス ServerThread では、クライアントを処理する次の行を実装します (コンパイルされていないコード、例外処理なし!):

Class ServerThread extends Thread {
    StreamConnection connection;

    public ServerThread(StreamConnection connection){
        this.connection = connection;
    }

    public void run() {
        ...

        connection.close();     //closing the connection...don't forget exception handling!
        System.out.println("INFO: Client disconnected");
    }
}

このコードの利点は何ですか? サーバーは、同時に 1000 のクライアントを処理できるようになりました。並列化ができました。これがサーバーの通常の動作です。スレッドのないサーバーは、靴のない靴下のようなものです...

次に、Java クライアントと Java サーバーを使用している場合は、ObjectOutputStream/ObjectInputStream という、より簡単にオブジェクトをサーバーに送信する方法を使用できます。連絡先を含む配列 (通常は ArraList を使用します) をサーバーに送信するだけで、その配列を読み取ることができます。サーバーのコードは次のとおりです (ここでもコンパイルされておらず、例外処理はありません)。

Class ServerThread extends Thread {
    StreamConnection connection;

    public ServerThread(StreamConnection connection){
        this.connection = connection;
    }

    public void run() {

        BufferedInputStream bis = new BufferedInputStream(this.connection.openInputStream());
        ObjectInputStream ois = new ObjectInputStream(bis);

        ArrayList contacts = (ArrayList) ois.readObject();  //this is a cast: don't forget exception handling!
        //You could also try the method ois.readUTF(); especially if you wanna use other non-Java clients

        System.out.println("INFO: Received from Bluetooth: " + contacts);
        this.connection.close();    //closing the connection...don't forget exception handling!
        //ois.close();      //do this instead of "this.connection.close()" if you want the connection to be open...i.e. to receive more data

        System.out.println("INFO: Client disconnected");

        //here you do whatever you wanna do with the contacts array, maybe add to your other contacts?
    }
}

Java では、ArrayList を含むすべてのクラスがオブジェクトです。また、オブジェクトの終了は切断と見なされるため、他に何もする必要はありません。

3 番目: 上記のサーバーは、Bluetooth 接続だけでなく、WLAN 接続にも使用します。次に、疑似コードのように、さまざまなスレッドを簡単に開始できますif(connection.isBluetooth()){//create a thread from BluetoothThread} else if(connection.isWLAN()){//create a thread from WLANsThread}。アプリの内容はわかりませんが、いつかデスクトップ PC に拡張したいので、WLAN を使用するのが適切です。また、とにかくクライアントに検証を組み込む必要があるため (「どの連絡先がどのサーバーに送信されるのか?」)、それが Bluetooth であろうと WLAN であろうと、buetooth の範囲が狭いとセキュリティを確保できないためです。 . ;)

最後に、質問について: 何かを取得するには、データ ソースおよび/またはクラス変数が必要です。連絡先を保存するファイルの短い例を次に示します (ただし、ローカルまたは別の場所のデータベースにすることもできます!):

public class MyApp(){
    ArrayList contacts;
    ...

    public void run(){                  //this happens when we start our app
        this.contacts = new ArrayList();
        FileReader fr = new FileReader ("C:\WhereverYourFileIs\Contacts.file");
        BufferedReader br = new BufferedReader(fr);
        //here you use a loop to read the contacts via "br" from the file and fill them into your array...I can't provide you more code, since the exact data structure is up to you.
    }

    //now we want to send our contacts array to the already connected server:
    public sendArrayToServer() {
        BufferedOutputStream bos = new BufferedOutputStream (this.connection.openOutputStream());
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this.contacts);
         //If you use readUTF() in the server, you need to call here something like oos.writeUTF(this.contacts.toString()); or even need to use another parser method which exactly creates the string you want.
        this.connection.close();    //closing the connection...don't forget exception handling!
        //oos.close();      //do this instead of "this.connection.close()" if you want the connection to stay open...
    }
}

サーバーでは、既に説明したように連絡先配列を読み取るだけです。それらの連絡先をどうするかは、あなた次第です。

これが問題を理解し、解決策を見つけるのに役立つことを願っています。プログラミングは、試行錯誤とコードの改善がすべてです。

編集:

議論の後、私は最終的に必要なものを見つけました: GUIController と呼ばれる別のスレッドと対話する BluetoothManager と呼ばれる 1 スレッド サーバーが必要です。とにかく頭の中で実装を行ったので、いくつかの説明とともに投稿できます。この場合、サーバーで別のスレッドを初期化する必要はないことに注意してください。これは、BluetoothManager が既にスレッドであり、同時に必要な接続は 1 つだけであるためです (それが「サーバー」であるかどうかという問題が残ります)。 、むしろ「レシーバー」と呼びます):

Public class BluetoothManager extends Thread{
    boolean running = true;    //this class variable will allow you to shut down the server correctly
    GUIController controller;

    public BluetoothManager(GUIController controller){
        this.controller = controller;  //this registers the GUIController in the BluetoothManager
    }

    public void stop(){    //this method will shut down the "server"
        this.running = false;
    }

    public void run() {
        this.localDevice = LocalDevice.getLocalDevice();
        this.localDevice.setDiscoverable(DiscoveryAgent.GIAC);
        this.server = (StreamConnectionNotifier) Connector.open(URL);
        while(running){
            StreamConnection connection = this.server.acceptAndOpen();  //This line will block until a connection is made...or running==false!
            System.out.println("INFO: Bluetooth client connected");
            BufferedInputStream bis = new BufferedInputStream(this.connection.openInputStream());
            ObjectInputStream ois = new ObjectInputStream(bis);
            ArrayList contacts = (ArrayList) ois.readObject();  //this is a cast: don't forget exception handling!
            System.out.println("INFO: Received from Bluetooth: " + contacts);
            this.connection.close();    //closing the connection...don't forget exception handling!
            System.out.println("INFO: Client disconnected");
            this.controller.refreshContacts(contacts);
        }
    } 
}

public class GUIController extends Thread implements Runnable {
    ArrayList contacts;   //also a HashMap may be appropriate
    BluetoothManager manager;

    public void run(){
        this.contacts = new ArrayList();
        FileReader fr = new FileReader ("C:\WhereverYourFileIs\Contacts.file");
        BufferedReader br = new BufferedReader(fr);
        //here you use a loop to read the contacts via "br" from the file and fill them into your array...I can't provide you more code, since the exact data structure is up to you.
    }

    public void startBluetoothManager(){    //starting the BluetoothManager
        this.manager = new BluetoothManager(this);
        this.manager.start();
    }

    public void abortBluetoothManager(){  //call this when clicking on the "Abort" button
        this.manager.stop();
        //now the next 2 lines you normally don't need...still may use it if you've problems shutting down the thread:
        // try{ this.manager.interrupt(); }   //we want to be 100% sure to shut down our thread!
        // catch(Exception e){}
        this.manager = null; //now the garbage collector can clean everything...byebye
    }

    public void refreshContacts(ArrayList contacts) {
        // synchronize(this.contactArray){  //no synchronisation needed if you have a GUI pop-up with an "Abort"-button!
        Iterator i = this.contacts.iterator();
        while(i.hasNext()){
            this.contacts.add(i.next());
        }
        //At the end you need remove the "Receiving message" pop-up together with the "Abort Receiving"-button, these are all class variables!
        // important note: If you have unique entries, you may need to replace them! In this case I suggest storing all contact objects better in a HashMap contacts, and use the unique ID as a key to find the element. And then you may prompt the user, if there are identical entries, to overwrite each entry or not. These things remain all up to you.
    }
}
//As always: This is no compiled code!!

GUIController は最初に BluetoothManager を実行し、startBluetoothManager()「連絡先を受信中」という通知と「受信を中止」ボタンを表示する以外は何もしません。BluetoothManager が終了したら、新しい連絡先を GUIController 内の既存の連絡先配列に追加するだけrefreshContacts(...)です。「Abort Reveiving」ボタンを押すと、すぐにabortBluetoothManager()メソッドが呼び出され、BluetoothManager に設定running=falseされてサーバーが終了し、スレッドが終了します。

このソリューションが解決する主な問題は、2 つのスレッドが互いに直接通信できないことです。を呼び出すとthread.start()、すべてのスレッドが独立します。そのため、BluetoothManager スレッドが GUIController スレッドに「終了しました!」と伝える可能性はありません。これらのスレッドができる唯一のことは、同じリソースを共有し、このリソースを介して通信することです。私たちの場合、それはcontactsGUIController の -ArrayList であり、最初に同期する必要があり、両方のスレッドで更新できると考えました (ただし、同時にではありません)。そして-ちょっと面白い-2番目の共有リソースがあります。実際には、runningそれをシャットダウンできるBluetoothManagerクラスのフラグです(ただし、running必要な同期は決してありません。

同期について:私はこの問題についてもっと考え、「同期(...)」呼び出しなしでも問題を解決できることを理解しました。したがって、ArrayList を同期させたくない場合は、次のようにする必要があります。サーバーの実行中は、[連絡先の受信] ポップアップと [受信の中止] ボタンのみを表示します。これが発生している間は、GUIController 内の contact-ArrayList にアクセスすることはありません。これはどういうわけか、実際の Java 同期を必要としない「本質的な同期」です。それでも、将来アプリを拡張しても何も起こらないことを 100% 確実にするために、同期を実装することはできます。

于 2013-02-07T19:09:53.000 に答える