Implementing the client-server connections

The Bluetooth client-server connections are implemented with the following classes:

  • ConnectionService —A ConnectionService is a runnable object that runs continuously. It listens for new inbound connection requests from a remote server, and uses its listener to handle the requests.

  • ClientConnectionHandler —When the MIDlet is run in the client mode, each connection is represented by a ClientConnectionHandler.

  • ClientConnectionHandlerListener —This interface describes the callbacks of the ClientConnectionHandler class.

  • ServerConnectionHandler—When the MIDlet is run in the server mode, each connection is represented by a ServerConnectionHandler instance. This class is very similar to the corresponding ClientConnectionHandler class.

  • ServerConnectionHandlerListener—This interface describes the callbacks of the ServerConnectionHandler class, and is very similar to the corresponding ClientConnectionHandlerListener class.

ConnectionService class

To implement the ConnectionService class:

  1. Create the ConnectionService.java class file.

  2. Import the required classes.

    import java.io.IOException;
    import javax.microedition.io.Connector;
    import javax.microedition.io.StreamConnection;
    import javax.microedition.io.StreamConnectionNotifier;
    
    import com.nokia.example.btsppecho.ClientForm;
    import com.nokia.example.btsppecho.LogScreen;
  3. Set ConnectionService to implement Runnable . MIDlets use the Runnable interface to implement classes that are executed in a thread.

    public class ConnectionService
            implements Runnable {
  4. Create the required variables, the ConnectionService class constructor, and a method for retrieving the client URL.

        private final ClientForm listener;
        private final String url;
        private StreamConnectionNotifier connectionNotifier = null;
        private volatile boolean aborting;
    
        public ConnectionService(String url,
                ClientForm listener) {
            this.url = url;
            this.listener = listener;
    
            LogScreen.log("ConnectionService: waiting to "
                    + "accept connections on '"
                    + url + "'\n");
    
            // start waiting for a connection
            Thread thread = new Thread(this);
            thread.start();
        }
    
        public String getClientURL() {
            return url;
        }
  5. Create a close method for closing the service thread.

        public void close() {
            if (!aborting) {
                synchronized (this) {
                    aborting = true;
                }
    
                // Ideally, one might want to give the run method's
                // loop a chance to abort before calling the
                // subsequent close, but the run loop is anyways
                // likely to be sitting on the acceptAndOpen
                // (i.e. blocked).
    
                try {
                    connectionNotifier.close();
                } catch (IOException e) {
                    // There is nothing very useful that
                    // we can do for this case.
                }
            }
        }
  6. Create a run method for starting the service and keeping it running.

        public void run() {
            aborting = false;
    
            try {
                connectionNotifier =
                        (StreamConnectionNotifier) Connector.open(url);
    
                // It might useful in some cases to add a service to the
                // 'Public Browse Group'. For example by doing something
                // approximately as follows:
                // -----------------------------------------------------
                // Retrieve the service record template
                // LocalDevice ld = LocalDevice.getLocalDevice();
                // ServiceRecord rec = ld.getRecord(connectionNotifier);
                // DataElement element =
                //             new DataElement(DataElement.DATSEQ);
                //
                // The service class for PublicBrowseGroup (0x1002)
                // is defined in the Bluetooth Assigned Numbers document.
                // element.addElement(new DataElement(DataElement.UUID,
                //                                    new UUID(0x1002)));
                //
                // Add to the public browse group:
                // rec.setAttributeValue(0x0005, element);
                // -----------------------------------------------------
            } catch (IOException e) {
                // ConnectionNotFoundException is an IOException
                String errorMessage =
                        "Error while starting ConnectionService: "
                        + e.getMessage();
    
                listener.handleError(null, errorMessage);
    
                aborting = true;
            } catch (SecurityException e) {
                String errorMessage =
                        "SecurityException while starting ConnectionService: "
                        + e.getMessage();
    
                listener.handleError(null, errorMessage);
    
                aborting = true;
            }
    
            while (!aborting) {
                try {
                    // 1. wait to accept & open a new connection
                    StreamConnection connection =
                            (StreamConnection) connectionNotifier.acceptAndOpen();
    
                    LogScreen.log("ConnectionService: new connection\n");
    
                    // 2. create a handler to take care of
                    // the new connection and inform
                    // the listener
                    if (!aborting) {
                        ClientConnectionHandler handler =
                                new ClientConnectionHandler(this,
                                connection,
                                listener);
                        listener.handleAcceptAndOpen(handler);
                    }
    
                    // One could consider exiting the
                    // ConnectionService when the Client
                    // reaches the maximum number of allowed
                    // open connections. In that case (i.e.
                    // when the maximum number of possible
                    // connections is already open), the
                    // ConnectionService will not be able
                    // to accept any new connections and one
                    // might possibly want to consider whether
                    // or not the ConnectionService thread
                    // could then be terminated.
                    //
                    // However, existing connections can also
                    // be disconnected (e.g., the Server is
                    // terminated or closes some/all of its
                    // existing connections). In that case,
                    // one may want to keep the
                    // ConnectionService alive and running:
                    // in order to accept later new connections
                    // without the need to restart the
                    // ConnectionService or MIDlet.
                    //
                    // (This MIDlet uses the latter approach.)
                } catch (IOException e) {
                    if (!aborting) {
                        String errorMessage =
                                "IOException occurred during "
                                + "accept and open: "
                                + e.getMessage();
    
                        listener.handleError(null, errorMessage);
                    }
                } catch (SecurityException e) {
                    if (!aborting) {
                        String errorMessage =
                                "IOException occurred during "
                                + "accept and open: "
                                + e.getMessage();
    
                        listener.handleError(null, errorMessage);
                    }
                }
            }
        }
    }

ClientConnectionHandler class

To implement the ClientConnectionHandler class:

  1. Create the ClientConnectionHandler.java class file.

  2. Import the required classes.

    import java.io.InputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Hashtable;
    import java.util.Enumeration;
    
    import javax.microedition.io.StreamConnection;
  3. Set ClientConnectionHandler to implement Runnable. MIDlets use the Runnable interface to implement classes that are executed in a thread.

    public class ClientConnectionHandler
            implements Runnable {
  4. Create the required variables, the ClientConnectionHandler class constructor, and a method for retrieving the current ClientConnectionHandlerListener.

        private final static byte ZERO = (byte) '0';
        private final static int LENGTH_MAX_DIGITS = 5;
        // this is an arbitrarily chosen value:
        private final static int MAX_MESSAGE_LENGTH =
                65536 - LENGTH_MAX_DIGITS;
        private final ClientConnectionHandlerListener listener;
        private final Hashtable sendMessages = new Hashtable();
        private StreamConnection connection;
        private InputStream in;
        private OutputStream out;
        private volatile boolean aborting;
    
        public ClientConnectionHandler(
                ConnectionService ConnectionService,
                StreamConnection connection,
                ClientConnectionHandlerListener listener) {
            this.connection = connection;
            this.listener = listener;
            aborting = false;
            in = null;
            out = null;
    
            // Our caller uses method 'start' to start the reader
            // and writer. (This allows the ConnectionService a
            // chance to add us to its list of handlers before
            // the reader and writer start running.)
        }
    
        ClientConnectionHandlerListener getListener() {
            return listener;
        }
  5. Create methods for starting and closing threads.

        public synchronized void start() {
            Thread thread = new Thread(this);
            thread.start();
        }
    
        public void close() {
            if (!aborting) {
                synchronized (this) {
                    aborting = true;
                }
    
                synchronized (sendMessages) {
                    sendMessages.notify();
                }
    
                if (out != null) {
                    try {
                        out.close();
                        synchronized (this) {
                            out = null;
                        }
                    } catch (IOException e) {
                        // there is nothing we can do: ignore it
                    }
                }
    
                if (in != null) {
                    try {
                        in.close();
                        synchronized (this) {
                            in = null;
                        }
                    } catch (IOException e) {
                        // there is nothing we can do: ignore it
                    }
                }
    
                if (connection != null) {
                    try {
                        connection.close();
                        synchronized (this) {
                            connection = null;
                        }
                    } catch (IOException e) {
                        // there is nothing we can do: ignore it
                    }
                }
            }
        }
  6. Create methods for receiving, reading, and sending messages.

        public void queueMessageForSending(Integer id, byte[] data) {
            if (data.length > MAX_MESSAGE_LENGTH) {
                throw new IllegalArgumentException(
                        "Message too long: limit is "
                        + MAX_MESSAGE_LENGTH + " bytes");
            }
    
            synchronized (sendMessages) {
                sendMessages.put(id, data);
                sendMessages.notify();
            }
        }
    
        public void run() {
            // the reader
    
            // 1. open the streams, start the writer
            try {
                in = connection.openInputStream();
                out = connection.openOutputStream();
    
                // start the writer
                Writer writer = new Writer(this);
                Thread writeThread = new Thread(writer);
                writeThread.start();
    
                listener.handleStreamsOpen(this);
            } catch (IOException e) {
                // open failed: close any connections/streams and
                // inform listener that the open failed
    
                close(); // also tells listener to delete handler
    
                listener.handleStreamsOpenError(this, e.getMessage());
                return;
            }
    
    
            // 2. wait to receive and read messages
            while (!aborting) {
                int length = 0;
                try {
                    byte[] lengthBuf = new byte[LENGTH_MAX_DIGITS];
                    readFully(in, lengthBuf);
                    length = readLength(lengthBuf);
                    byte[] temp = new byte[length];
                    readFully(in, temp);
    
                    listener.handleReceivedMessage(this, temp);
                } catch (IOException e) {
                    close();
                    if (length == 0) {
                        listener.handleClose(this);
                    } else {
                        // we were in the middle of reading...
                        listener.handleErrorClose(this, e.getMessage());
                    }
                }
            }
        }
    
        private static void readFully(InputStream in, byte[] buffer)
                throws IOException {
            int bytesRead = 0;
    
            while (bytesRead < buffer.length) {
                int count = in.read(buffer,
                        bytesRead,
                        buffer.length - bytesRead);
    
                if (count == -1) {
                    throw new IOException("Input stream closed");
                }
                bytesRead += count;
            }
        }
    
        private static int readLength(byte[] buffer) {
            int value = 0;
    
            for (int i = 0; i < LENGTH_MAX_DIGITS; ++i) {
                value *= 10;
                value += buffer[i] - ZERO;
            }
            return value;
        }
    
        private void sendMessage(OutputStream out, byte[] data)
                throws IOException {
            if (data.length > MAX_MESSAGE_LENGTH) {
                throw new IllegalArgumentException(
                        "Message too long: limit is: "
                        + MAX_MESSAGE_LENGTH + " bytes");
            }
            byte[] buf = new byte[LENGTH_MAX_DIGITS + data.length];
            writeLength(data.length, buf);
            System.arraycopy(data, 0, buf, LENGTH_MAX_DIGITS, data.length);
            out.write(buf);
            out.flush();
        }
    
        private static void writeLength(int value, byte[] buffer) {
            for (int i = LENGTH_MAX_DIGITS - 1; i >= 0; --i) {
                buffer[i] = (byte) (ZERO + value % 10);
                value = value / 10;
            }
        }
  7. The ClientConnectionHandler class has an inner class called Writer, which is used for handling message sending.

        private class Writer implements Runnable {
    
            private final ClientConnectionHandler handler;
    
            Writer(ClientConnectionHandler handler) {
                this.handler = handler;
            }
    
            public void run() {
                while (!aborting) {
                    synchronized (sendMessages) {
                        Enumeration e = sendMessages.keys();
                        if (e.hasMoreElements()) {
                            // send any pending messages
                            Integer id = (Integer) e.nextElement();
                            byte[] sendData = (byte[]) sendMessages.get(id);
                            try {
                                sendMessage(out, sendData);
    
                                // remove sent message from queue
                                sendMessages.remove(id);
    
                                // inform listener that it was sent
                                listener.handleQueuedMessageWasSent(
                                        handler,
                                        id);
                            } catch (IOException ex) {
                                close(); // stop the networking thread
    
                                // inform that we got an error close
                                listener.handleErrorClose(
                                        handler,
                                        ex.getMessage());
                            }
                        }
    
                        if (sendMessages.isEmpty()) {
                            try {
                                sendMessages.wait();
                            } catch (InterruptedException ex) {
                                // this can't happen in MIDP: ignore it
                            }
                        }
                    }
                }
            }
        }
    }

ClientConnectionHandlerListener class

To implement the ClientConnectionHandlerListener class:

  1. Create the ClientConnectionHandlerListener.java class file.

  2. Define the callback methods for theClientConnectionHandler.

    public interface ClientConnectionHandlerListener {
        // The handler's accept and open of a new connection
        // has occurred, but the I/O streams are not yet open.
        // The I/O streams must be open to send or receive
        // messages.
    
        public void handleAcceptAndOpen(ClientConnectionHandler handler);
    
        // The handler's I/O streams are now open and the
        // handler can now be used to send and receive messages.
        public void handleStreamsOpen(ClientConnectionHandler handler);
    
        // Opening of the handler's I/O streams failed. The handler has
        // closed any connections or streams that were open.
        // The handler should not be used anymore,
        // and should be discarded.
        public void handleStreamsOpenError(ClientConnectionHandler handler,
                String errorMessage);
    
        // The handler got an inbound message.
        public void handleReceivedMessage(
                ClientConnectionHandler handler,
                byte[] messageBytes);
    
        // A message that had been previously queued for sending
        // (identified by id) by the handler, has been sent successfully.
        public void handleQueuedMessageWasSent(
                ClientConnectionHandler handler,
                Integer id);
    
        // The handler has closed its connections and streams.
        // The handler should not be used anymore, and should be discarded.
        // Only handlers which have previously called 'handleOpen' may
        // call 'handleClose', and only just once.
        public void handleClose(ClientConnectionHandler handler);
    
        // An error related to the handler occurred. The handler
        // has closed the connection, and the handler should no
        // longer be used.
        public void handleErrorClose(ClientConnectionHandler handler,
                String errorMessage);
    }