Логин:   Пароль:




Новости
Рассылки
Форум
Поиск


Java
- Апплеты
- Вопрос-ответ
- Классы
- Примеры
- Руководства
- Статьи
- IDE
- Словарь терминов
- Скачать

Мобильная Java
- Игры
- Примеры
- Статьи
- WAP, WML и пр.

JavaScript
- Вопрос-ответ
- Примеры
- Статьи

Веб-мастеринг
- HTML
- CSS
- SSI

Разминка для ума
Проекты
Книги
Ссылки
Программы
Юмор :)




Rambler's Top100

Мобильная Java: СтатьиИспользование J2ME. Часть 2

Использование J2ME. Часть 2

Обзор

В этой части статьи разговор пойдет о коммуникационных возможностях конфигурации CLDC вообще и профиля MIDP в частности. Будет дан небольшой обзор GCF и пример ее использования в реальном приложении.

Generic Connection Framework

Как уже было сказано в предыдущей части статьи, основная цель, которую преследует GCF - это создание на программном уровне единой абстракции для любых типов соединений. Диаграмма классов GCF представлена на рис. 1.


Рисунок 1.

Как Вы можете видеть, классов действительно немного и самое интересное, что их действительно хватает для работы с любыми типами соединений по любым протоколам.

И так, соединение создается с помощью вызова статического метода класса Connector:


Connector.open("<protocol>:<address;><parameters;>");

Синтаксис строки должен соответствовать RFC2396 [2]. <protocol;> - протокол, с помощью которого будет выполнена попытка установить соединение, <address;> - адрес ресурса, формат адреса зависит от используемого протокола, <parameters;> - параметры соединения. Предполагается, что все это будет работать примерно так:

  1. при вызове open(), будет произведен разбор строки и определение запрашиваемого протокола;
  2. будет выполнена попытка создать экземпляр класса, который реализует запрашиваемый протокол;
  3. если такой протокол найден, ему будут переданы поля <address;> и <parameters;>.

Примеры создания соединений [1] (на уровне CLDC отсутствует реализация каких-либо протоколов - реализация выполняется на уровне профиля):

HTTP
Connector.open("http://www.foo.com");
Sockets
Connector.open("socket://129.144.111.222:9000");
Communication ports
Connector.open("comm:0;baudrate=9600");
Datagrams
Connector.open("datagram://:1234");
Files
Connector.open("file:/foo.dat");

Ниже будет дано очень краткое описание классов GCF, более полное описание Вы можете найти в спецификации [1]:

Connection
Самый простой класс представляющий соединение, которое можно только открыть и закрыть.

InputConnection
Представляет соединение, с помощью которого можно принимать данные.

OutputConnection
Представляет соединение, с помощью которого можно передавать данные.

StreamConnection
Представляет соединение, с помощью которого можно и передавать и принимать данные.

ContentConnection
К двусторонней передаче данных добавляет возможность получение некоторой мета-информации о соединении (для HTTP протокола, например, можно получить тип используемой кодировки).

StreamConnectionNotifier
Данный интерфейс позволяет ждать установление соединения.

DatagramConnection
Этот интерфейс предоставляет доступ к протоколам датаграммного обмена данными (например UDP в стеке протоколов TCP/IP). Для работы с такими типами соединений существует дополнительный класс Datagram, который инкапсулирует в себе буфер с данными и адрес. Отметим, что данный интерфейс позволяет работать как в "клиентском", так и в "серверном" режиме, т.е. по сути позволяет использовать "серверные сокеты" или "слушатели". Еще раз хочется повторить замечание, которое уже было сделано в предыдущей статье: профиль MIDP обязан поддерживать только протокол HTTP, но может поддерживать и датаграммные протоколы. В случае поддержки таких протоколов это должно быть реализовано через GFC. Такая дискриминация по отношению к отличным от HTTP протоколам связана с тем, что не все беспроводные сети базируются на стеках протоколов, способных поддерживать датаграмные соединение или соединения с помощью сокетов. Реализация же HTTP может быть сделана поверх любого доступного в данной сети транспортного протокола.

Проектируем коммуникационную часть

Теперь, используя знания полученные в предыдущей главе нам не составит труда реализовать связку "клитент-сервер", используя push модель. Для справки, push модель в отношении коммуникационной задачи, это когда сервер "заталкивает" (pushing) событие в клиента (существует еще pull модель - клиент сам "вытягивает" (pulling) событие с сервера). В общем виде push модель с помощью sequence диаграммы представлена на рис. 2.

Рисунок 2
Рисунок 2.

Именно такую модель будем использовать и мы, но для начала нужно разработать протокол обмена сообщениями. Наша спецификация протокола будет состоять из нескольких классов и некоторого числа соглашений по использованию этих классов.

Протокол передачи сообщений

Немного терминологии. Мобильным клиентом или просто клиентом мы будем называть проектируемое нами программное обеспечение (ПО) для мобильного устройства. Пользователем будем называть человека, который пользуется этим ПО. Сервером будем называть ПО обеспечивающее взаимодействие клиентов с системой обмена сообщениями (собственно сам сервер и реализует эту систему). Чтобы грамотно спроектировать протокол обмена сообщениями сначала нужно определиться с требованиями к нему, а также описать основные сценарии его работы (прецеденты или use case).

Требования:

  • возможность авторизованного входа и выхода их системы;
  • возможность передачи сообщений от одного мобильного клиента другому;
  • по запросу снабжать клиента списком других пользователей системы с возможностью задания критерия отбора;
  • возможность извещения клиента об ошибочном сообщении (адресат недоступен);

Прецеденты:

  1. Вход в систему: клиент посылает сообщение серверу содержащее имя и пароль, сервер проверяет правильность имени и пароля в соответствии со своей внутренней базой и посылает клиенту либо подтверждение о входе, либо отказ, клиент добавляется во внутреннюю таблицу он-лайн клиентов;
  2. Посылка сообщения: клиент посылает сообщение серверу содержащее имя получателя и текст сообщения, сервер проверяет доступность адресата и пересылает ему сообщение, если адресат недоступен, сервер посылает сообщение об ошибке отправителю;
  3. Запрос списка клиентов: клиент посылает сообщение с запросом списка, сервер формирует список в соответствии с критерием ипосылает список клиенту;
  4. Выход клиента из системы: клиент посылает сообщение серверу с запросом о выходе из системы, сервер модифицирует внутреннюю таблицу об он-лайн клиентах;
  5. Выход сервера из системы: сервер рассылает всем доступным клиентам уведомление о своем выходе из системы, все клиенты переходят в отсоединенное состояние.

Реализация

Диаграмма общих для клиента и сервера классов, относящихся к протоколу передачи данных, представлена на рис. 3.

Рисунок 3
Рисунок 3.

Message - класс, инкапсулирующий всю семантику сообщения, MobileProtocol - интерфейс задающий методы, которые должен реализовывать класс, который хочет выступать в роли протокола кодирования/декодирования сообщений в/из массива байт, MessageReceivedListener - интерфейс, который должен реализовывать класс, который хочет быть уведомленным о приходе нового сообщения, более подробно на нем мы остановимся немного познее.

SimpleMobileProtocol - класс реализующий процесс кодирования декодирования сообщений самым простым образом. Для того чтобы клиент и сервер говорили на одном языке они должны использовать одинаковый протокол, этого можно дбиться двумя способами:

  1. Клиент и сервер используют один и тотже класс, реализующий протокол;
  2. Классы разные, но используют один и тотже формат кодирования;

Мы остановимся на первом варианте - он более честный. Правда здесь сразу возникает небольшая проблема - понятно, что для кодирования объекта класса Message в массив байт (или поток, кому как нравиться) в J2SE удобно использовать сериализацию, но механизм сериализации отсутствует в J2ME, отсутствуют классы типа ObjectOutputStream. Зато в J2ME есть DataOutputStream, его и будем использовать.

С учетом всех вышеописанных замечаний реализация класса SimpleMobileProtocol выглядит следующим образом:

public class SimpleMobileProtocol implements MobileProtocol
{
 public SimpleMobileProtocol()
 {
 }

 public Message decodeMessage(byte[] data) throws IOException
 {
  ByteArrayInputStream bais = new ByteArrayInputStream(data);
  DataInputStream dis = new DataInputStream(bais);

  Message message = new Message();
  message.setType(dis.readInt());
  message.setSender(dis.readUTF());
  message.setRecipient(dis.readUTF());
  message.setPassword(dis.readUTF());
  message.setInfo(dis.readUTF());

  Vector users = new Vector();
  int size = dis.readInt();

  for (int i = 0; i < size; i++)
  {
   users.addElement(dis.readUTF());
  }

  message.setUsers(users);

  return message;
 }

 public byte[] encodeMessage(Message message) throws IOException
 {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  DataOutputStream das = new DataOutputStream(baos);

  das.writeInt(message.getType());
  das.writeUTF(message.getSender());
  das.writeUTF(message.getRecipient());
  das.writeUTF(message.getPassword());
  das.writeUTF(message.getInfo());

  Vector users = message.getUsers();

  das.writeInt(users.size());
  for (int i = 0; i < users.size(); i++)
  {
   das.writeUTF((String)users.elementAt(i));
  }

  return baos.toByteArray();
 }
}


Я думаю каких-то особенных комментариев сдесь не нужно делать, отмечу только, что вместо метода DataOutputStream.writeChars(String) используется метод writeUTF(String). Это связано с тем, что второй записывает в поток информацию о длине строки, что необходимо для ее успешного декодирования. Вектор строк сохраняется в виде <кол-во_элементов>[строка1[строка2[строка3..]..]..]

Серверный и клиентский коннекторы

Теперь начнем использовать только что описанный протокол. Заметьте, что класс SimpleMobileProtocol является частью всего протокола и отвечает только за кодирование/декодирование сообщений, еще к протоколу относится некоторые соглашения и требования, описанные выше, именно их мы и попытаемя дальше реализовать.

Для сервера и клиента все вопросы обмена сообщениями вынесены в отдельные классы - клиентский и серверный коннекторы (см. рис 4).

Рисунок 4
Рисунок 4.

Оба класса наследуются от класса Thread (хотя на диаграмме это один и тотже класс, на самом деле в J2SE и J2ME эти классы различаются) и аггрегируют интерфейс MessageReceivedListener. Классы имеют метод для посылки сообщения sendMessage(Message) и умеют уведомлять о приходе нового сообщения посредством вызова метода MessageReceivedListener.messageReceived(Message).

На примере процедуры входа пользователя в систему, все это хозяйство работает следующим образом (см. рис.5):

Рисунок 5
Рисунок 5. (нажмите на рисунок для увеличения)
  1. создание и инициализация сервером и клиентом своих коннекторов. Обычно эти шаги разнесены по времени - сначала запускают сервер, потом клиента
  2. -"-
  3. -"-
  4. -"-
  5. клиент посылает запрос на вход в систему (TYPE_LOGIN), передавая серверу свое имя и пароль
  6. клиентский коннектор преобразует сообщение в массив байт для отправки серверу
  7. посылка пакета серверу по протоколу UDP
  8. серверный коннектор декодирует сообщение из массива принятых байтов
  9. уведомление сервера о получении нового сообщения. На этом шаге на самом деле сервер ничего не делает, этот вызов отладочный и используется для отображения сервером принимаемых сообщений на консоль
  10. коннектор проверяет права пользователя. При использовании SimpleUserDatabase проверяется только наличие пользователя в базе и совпадение принятого пароля с паролем из базы
  11. в случае удачной проверки прав пользователя серверный коннектор посылает клиентскому коннектору сообщение об удачном входе в систему
  12. клиентский коннектор уведомляет MIDlet о приему нового сообщения
Ниже приведен код клиентского коннектора с комментариями.
public class ClientConnector extends Thread
{
 /** слушатель, который хочет получать
     уведомление о приходе нового сообщения */
 private MessageReceivedListener listener = null;
 /** серверный сокет для получения входящих датаграмм */
 private javax.microedition.io.DatagramConnection 
                          clientConnection = null;
 /** имя хоста сервера */
 private String serverHost;
 /** номер порта, на котором слушает сервер */
 private int serverPort;
 /** номер порта, на котором должен слушать клиент */
 private int clientPort;
 /** протокол, для кодирования/декодирования сообщений */
 private MobileProtocol protocol;

 public ClientConnector(String serverHost,
                        int serverPort,int clientPort,
                        MessageReceivedListener listener,
                        MobileProtocol protocol) 
                                    throws IOException
 { 
  this.serverHost = serverHost;
  this.serverPort = serverPort;
  this.clientPort = clientPort;
  this.listener = listener;
  this.protocol = protocol;
/* создание листенера входящих датаграм от сервера */
  clientConnection = 
           (javax.microedition.io.DatagramConnection)
  Connector.open("datagram://:" + 
                   Integer.toString(clientPort));
 }

/**
 * установка класса, который должен быть 
 * уведомлен о приходе нового сообщения
 */
 public void addMessageReceivedListener
          (MessageReceivedListener listener)
 {
  this.listener = listener;
 }

 public void run()
 {
  Datagram datagram;
  try
  {
 /* зацикливаем нить, пока есть
    кому получать входящие сообщения */
   while (listener != null)
   {
    /* создаем датаграму */
    datagram =clientConnection.newDatagram(
              clientConnection.getNominalLength());
                /* ждем входящего сообщения */
    clientConnection.receive(datagram);
    if (listener == null)
    {
     return;
    }
    else
    {
/* расшифровываем сообщение с помощью установленного
      протокола и уведомляем об этом получателя*/
     listener.messageReceived(
        protocol.decodeMessage(datagram.getData()));
    }
   }
  } 
  catch (Exception e)
  {
   e.printStackTrace();
   return;
  } 
  finally
  {
   if (clientConnection != null)
   {
    try
    {
     clientConnection.close();
    }
    catch (Exception e)
    {
     e.printStackTrace();
    }
   }
  }
 }

/**
 * метод для отправки сообщений серверу
 */
 public void sendMessage(Message message) throws IOException
 {
 /* кодируем сообщние */
  byte[] data = protocol.encodeMessage(message);
 /*  создаем соединение с сервером */
  DatagramConnection serverConnection =
  (javax.microedition.io.DatagramConnection)Connector.open(
       "datagram://" + serverHost + 
       ":"+ Integer.toString(serverPort));
 /*  создаем датаграму наполненную полезной информацией */
  Datagram datagram = 
     serverConnection.newDatagram(data, data.length);
        /* отправляем датаграму и закрываем соединение*/
  serverConnection.send(datagram);
  serverConnection.close();
 }
}


Описание серверного коннектора и работы серверного приложения целиком будет дано в одной из последующих глав. Также в последующих главах будет рассмотрено

  • пакет javax.microedition.rms - служит для долговременного хранения какой-либо информации в постоянной памяти мобильного устройства
  • пакет javax.microedition.lcdui - служит для создания интерфейса пользователя
  • пакет javax.microedition.midlet - служит для создания самого MIDP приложения

Ресурсы

  1. Connected, Limited Device Configuration. Specification Version 1.0. (http://www.sun.com/software/communitysource/j2me/cldc/)
  2. Uniform Resource Identifiers (URI): Generic Syntax (http://www.ietf.org/rfc/rfc2396.txt)
  3. Mobile Information Device Profile (JSR-37). JCP Specification Version 1.0a. (http://www.sun.com/software/communitysource/midp/)


<< Часть 1 | Часть 3 >>


Автор: Вадим Гуров


Д. Гудман
"JavaScript и DHTML. Сборник рецептов. Для профессионалов"
Подробнее>>
Заказать>>


Любош Бруга
"Java по-быстрому. Практический экспресс-курс"
Подробнее>>
Заказать>>

Узнай о чем ты на самом деле сейчас думаешь тут.


[an error occurred while processing this directive]



Apache Struts 2.0.11
Apache MyFaces Trinidad Core 1.2.3.
Sun переводит мобильные устройства с Java ME на Java SE
Хакерская атака!