Header Sep

Java Tips, Tricks & Code

我的评分 用户评价
登录票选本页

February 2006

Advice on managing connections between Bluetooth SDP records and a game server

 

When browsing various JSR 82 MIDlets on the market, it was noticed that some of the MIDlets are not handling the Service Discovery Protocol (SDP) records in a proper manner. SDP records in the Bluetooth sphere are quite difficult to grasp, but in JSR 82 it doesn't have to be so difficult.

Here's a short article to shed some light on a common issue with SDP records.

Let's briefly touch the subject about why the SDP record is needed. The SDP record is a record that identifies a specific service on a device. Without the SDP record it would not be possible for one mobile phone to detect services on another device. Most phones have more than one SDP record, e.g. they are likely to have records for L2CAP and Serial Port.

The main issue seen with SDP records is that many developers forget to remove the SDP record from the dB. This means that the end-user will not be able to re-connect to a running game.

Below are two simple pictures illustrating the problem.

    Figure 1: SDP Connection succeeds.

As we can see in Figure 1, two MIDlets are trying to connect. The connection is a success.
 

    Figure 2: SDP Connection fails.

In Figure 2 we can see that a MIDlet is again trying to connect (perhaps the same user or a new user). The connection is a failure since the MIDlet tries to connect to a SDP record that has no listeners and the Bluetooth stack is left with no option but to reject the connection. What often happens is that the client MIDlet receives both SDP records, but chooses to connect to the first one simply because it only expects one SDP record on each server.

In the following code example, a very simple server is shown. The example focuses on the necessary close() calls.

Example:

public class SEMCSPPServer extends Thread
{
  private StreamConnectionNotifier server = null;
  private StreamConnection sppConnection = null;

  public SEMCSPPServer()
  {
    // This will create create an SDP record in the dB
    try
    {
      server = (StreamConnectionNotifier)Connector.open( "btspp://localhost:ea834a8566aa4e0fb02ce4c1a53700c9;name=SomeServer" );
    }
    catch( Exception e ) {}
  }

  public void run()
  {
    // Wait for connections
    try
    {
      sppConnection = server.acceptAndOpen();
    }
    catch( Exception e ) { e.printStackTrace(); }

    // Let the server do something fun here

    try
    {
      // Open the Streams to be used for communications
      InputStream in = sppConnection.openInputStream();
      OutputStream out = sppConnection.openOutputStream();
         
      // Let the server do something fun here
      while()
      {
      }

      // Server is done, now cleanup


      // Close the Streams
      try
      {
        in.close();
      }
      catch( IOException ioe ) {}
      try
      {
        out.close();
      }
      catch( IOException ioe ) {}

      in = null;
      out = null;
    }
    catch( Exception e ) {}

    // Close the Connection
    try
    {
      sppConnection.close();
    }
    catch( IOException ioe ) {}
    sppConnection = null;

    // To make it possible for a client to re-connect
    //   we need to remove the current SDP record
    // If the MIDlet ends here we SHOULD still
    //  close the notifier, but the MIDlet environment will
    //  clean-up after us
    server.close();
  } // run
}

Naturally you can have some kind of Server Handler that handles all server connections and make re-use of the SDP record, i.e. there is always a Server Handler that listens for connections. For example, in a multiplayer Bluetooth game where people are allowed to enter and exit at will.

Server Handler example:

// A simple server handler
public class SEMCSPPServerHandler
{
  private StreamConnectionNotifier server = null;
  private StreamConnection sppConnection = null;

  public SEMCSPPServerHandler()
  {
    // This will create create an SDP record in the dB
    try
    {
      server = (StreamConnectionNotifier)Connector.open( "btspp://localhost:ea834a8566aa4e0fb02ce4c1a53700c9;name=SomeServer" );
    }
    catch( Exception e ) {}


    while( true )
    {
      // Wait for connections
      try
      {
        sppConnection = server.acceptAndOpen();
      }
      catch( Exception e ) { e.printStackTrace(); }

      if( sppConnection != null )
      {
        SEMCSPPServer sp = new SEMCSPPServer( sppConnection );
        sp.start();
        sp = null;
      }
      // The server handler is now ready to deal with new connections
      // Note, there is no need to create a new SDP record
    }

    // Remove the SDP record
    server.close();
  }
}

// A simple server class to deal with 1 connection
public class SEMCSPPServer extends Thread
{
  private StreamConnection sppConnection = null;

  public SEMCSPPServer( StreamConnection sppConnection )
  {
    this.sppConnection = sppConnection;
  }

  public void run()
  {
    try
    {
      // Open the Streams to be used for communications
      InputStream in = sppConnection.openInputStream();
      OutputStream out = sppConnection.openOutputStream();
         
      // Let the server do something fun here
      while()
      {
      }
      // Server is done, now cleanup


      // Close the Streams
      try
      {
        in.close();
      }
      catch( IOException ioe ) {}
      try
      {
        out.close();
      }
      catch( IOException ioe ) {}

      in = null;
      out = null;
    }
    catch( Exception e ) {}

    // Close the Connection
    try
    {
      sppConnection.close();
    }
    catch( IOException ioe ) {}
    sppConnection = null;
    // The server is no longer active
  } // run
}


Lesson to be learned
If the Connector.open() call is not handled with care, it will not be possible to re-connect to that SDP record without exiting the MIDlet (in which case the SDP dB is clean again). In real life, you will have to exit the game and start it again, a frustrating move for end-users.

Of course it is possible to have more than one SDP record in your application, but for the proper functionality make sure that the MIDlet is listening to all records.

 

我的评分 用户评价
登录票选本页