3

我有一个蓝牙服务器从客户端,手机接收数据。我正在使用的代码如下所示

@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(). 它用于通过蓝牙从手机接收联系人。当服务器发送时,电话被告知发送联系人--#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

我会给你一些建议。我的示例不是工作代码,只是您的工作基础。

首先,我强烈建议您在服务器中使用线程。每次客户端连接到服务器时,您都会创建一个新线程,其参数包含启动它所需的所有数据:

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");
    }
}

这段代码有什么好处?您的服务器现在能够同时处理一千个客户端。您已经进行了并行化,这就是服务器通常的工作方式!没有线程的服务器就像没有鞋子的袜子......

其次,如果您有 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。并且由于对象的结束将被视为断开连接,因此您无需执行任何其他操作。

第三:您使用上述服务器不仅用于蓝牙连接,还用于 WLAN 连接,aso。然后您可以轻松地启动不同的线程,例如在伪代码if(connection.isBluetooth()){//create a thread from BluetoothThread} else if(connection.isWLAN()){//create a thread from WLANsThread}中。我不知道您的应用程序是关于什么的,但也许有一天您想将其扩展到台式机,因此使用 WLAN 将是正确的选择。还因为您无论如何都需要在客户端中构建验证(“哪些联系人将被发送到哪个服务器?”),无论是蓝牙还是WLAN,因为蓝牙的低范围不能给你任何安全性. ;)

第四,最后关于您的问题:要获得某些东西,您需要有一个数据源和/或一个类变量。这是一个简短的示例,其中包含一个存储联系人的文件(但它也可以是数据库......本地或其他地方!):

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...
    }
}

现在在服务器中,您只需读出上面已经描述的联系人数组。您如何处理这些联系人仍取决于您。

希望这可以帮助您了解您的问题并找到解决方案。编程就是反复试验……和改进你的代码。

编辑:

经过我们的讨论,我终于找到了你需要的东西:你需要一个名为 BluetoothManager 的单线程服务器,它与另一个名为 GUIController 的线程交互。现在,既然我无论如何都在脑海中完成了实现,我可以为您发布它,并附上一些解释。请注意,在这种情况下,您不需要在服务器中初始化另一个线程,因为 BluetoothManager 已经是一个线程,而且您同时只需要一个连接(问题仍然存在,如果那是一个“服务器” ,我宁愿称它为“接收器”):

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(),除了显示通知“接收联系人”和“中止 Reveiving”按钮外,什么都不做。当 BluetoothManager 完成后,他只需通过调用将新联系人添加到 GUIController 内的现有联系人数组中refreshContacts(...)。如果您按下“Abort Reveiving”按钮,您会立即调用该abortBluetoothManager()方法,该方法设置running=false在 BluetoothManager 中以结束服务器并完成线程。

该解决方案解决的主要问题:两个线程无法直接相互通信!一旦你调用thread.start(),每个线程都是独立的。这就是为什么 BluetoothManager 线程不可能告诉 GUIController 线程“我已经完成了!”。这些线程唯一能做的就是共享相同的资源,并通过这些资源进行通信。在我们的例子中,它是contactsGUIController 中的 -ArrayList,首先我认为它需要同步并且可以由两个线程更新(但不能同时更新)。而且 - 有点有趣 - 还有第二个共享资源,它实际上是runningBluetoothManager 类中的标志,可以将其关闭(但从来不需要任何同步running

现在关于同步:我更多地考虑了这个问题并理解了,您也可以在没有任何“同步(...)”调用的情况下解决您的问题。因此,如果您不想同步 ArrayList,则必须这样做:在服务器运行时,您只显示“接收联系人”弹出窗口和“中止接收”按钮。发生这种情况时,您永远不会访问 GUIController 内的contact-ArrayList。这在某种程度上是一种“内在同步”,不需要真正的 Java 同步。您仍然可以实现同步,只是为了 100% 确保将来扩展应用程序时不会发生任何事情。

于 2013-02-07T19:09:53.000 回答
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 回答