Разрабатывая интерфейс для работы с сетью, разработчики J2ME стремились добиться универсальности и простоты. Программист не должен заботиться о деталях установки соединения. Реализованная в J2ME модель работы с сетью упрощена по сравнению с J2SE. Для работы с сетью в J2ME используется специальный MIDP API: Generic Connection Framework (GCF). Поддержка GCF осуществляется на уровне конфигурации. Этот набор интерфейсов расположен в пакете javax.microedition.io.
Основу GCF составляет класс Connector и набор интерфейсов. Класс Connector является основой для образцов подключения. Интерфейсы определяют типы поддерживаемых соединений.
Ниже приведен код, обеспечивающий простейшее HTML соединение.
HttpConnection MyCon=(HttpConnection) Connector.open("http://www.mobilab.ru", Connector.READ_WRITE,true);
Класс Connector определяет, какой тип соединения нужно создать, с помощью специальной URL строки. В данном случае это "http://www.mobilab.ru". URL строка состоит из трех частей: схемы, адреса и параметров. Необходимой из этих частей является только схема. Адрес и параметры можно упустить.
{схема}: [{адрес}] [{параметры}]
В приведенном листинге URL состоит из схемы "http" и адреса "www.mobilab.ru". Параметры опущены. GCF не ограничивается поддержкой HTTP. Возможны и другие типы соединений:
HTTP | http://www.host.com: 8080 |
Socket | socket://host.com:80 |
Socket Listener | socket://:1234 |
Datagram Sender | datagram://host.com:9001 |
Datagram Listener | datagram://: 9001 |
File | file:/myfile.txt |
Comm Port | comm:com0;baudrate=19200;parity=odd |
Разрабатывая долгое время Java программы, Вы вполне могли не сталкиваться с понятием датаграмма. По сравнению с обычным TCP соединением, дейтаграммы позволяют более быстро передавать данные. Самым распространенным протоколом дейтаграмм является User Datagram Protocol (UDP), однако, поскольку все протоколы дейтаграмм строятся на одних и тех же базовых принципах, GCF поддерживает их непосредственно.
В основе технологии дейтаграмм лежит передача данных без установки соединения. Дейтаграммы передаются в сеть "вслепую", то есть факт доставки письма адресату не проверяется. После того как сообщение отправлено, приложение уже не заботит его судьба. В случае если связь плохая, дейтаграмма вполне может не дойти до адресата. Если Вы отправляете несколько дейтаграмм, то совсем не факт, что они придут адресату в том порядке, в котором Вы их отправляли. Необходимая проверка может быть выполнена на уровне приложения.
Приведенный ниже пример показывает как можно создать дейтаграмму и передать ее по указанному IP адресу.
try{ DatagramConnection dgc=(DatagramConnection) Connector.open("datagram://localhost:9001"); try{ byte[] payload="Test Message".getBytes(); Datagram datagram= dgc.newDatagram(payload, payload.length); dgc.send(datagram); } finally{ dgc.close(); } } catch(IOException x){ x.printStackTrace(); }
В примере создается дейтаграмма с текстом "Test Message", которая передается на порт 9001 локального устройства (телефон передает дейтаграмму сам себе).
Это приложение будет работать правильно, даже если у вас не запущена программа, прослушивающая порт 9001, поскольку сразу после отправления приложение забывает о дейтаграмме, не требуя от адресата никакого подтверждения.
Ниже приведен код приложения, получающего отправленную дейтаграмму:
try{ DatagramConnection dgc=(DatagramConnection) Connector.open("datagram://:9001"); try{ int size=100; Datagram datagram= dgc.newDatagram(size); dgc.receive(datagram); System.out.println( new String(datagram.getData()).trim()); } finally{ dgc.close(); } } catch(IOException x){ x.printStackTrace(); }
В приведенном примере устанавливается соединение с портом 9001. Предполагается, что размер дейтаграммы не может превышать 100 байт. В случае превышения лишние символы просто обрезаются. После того как дейтаграмма создана, вызывается метод receive(), который переводит поток в состояние ожидания до тех пор, пока не будет получено сообщение. После получения из дейтаграммы извлекается полезная информация и выводится на печать.
Другим распространенным типом соединения является TCP сокет-соединение. Передача данных при работе с сокетами происходит на основе подключения. Это значит, что отправитель и получатель должны установить между собой канал связи для обмена данными. Это как звонок по телефону. Если ваш друг не снял трубку, Вы не сможете передать ему информацию. Использование сокетов подразумевает гарантированную доставку данных в том порядке, в котором они были отправлены.
Приведенный ниже код показывает, как организовать прослушивание порта.
try { ServerSocketConnection ssc=(ServerSocketConnection) Connector.open("socket://:9002"); StreamConnection sc=null; InputStream is=null; try{ sc= ssc.acceptAndOpen(); is= sc.openInputStream(); int ch=0; StringBuffer sb=new StringBuffer(); while((ch= is.read())!=-1){ sb.append((char)ch); } System.out.println(sb.toString()); } finally{ ssc.close(); sc.close(); is.close(); } } catch(IOException x){ x.printStackTrace(); }
В приведенном примере ServerSocketConnection открывает порт 9002. Этот тип соединения используется для единственной цели - прослушивания входящей сокет информации. После вызова метода acceptAndOpen() поток переходит в состояние ожидания. После того как соединение установлено, этот метод возвращает экземпляр класса SocketConnection. С этого момента данные можно считывать из входящего потока.
Ниже приведен код, который инициализирует соединение с клиентом.
try{ SocketConnection sc=(SocketConnection) Connector.open("socket://localhost:9002"); OutputStream os=null; try{ os= sc.openOutputStream(); byte[] data="Hello from a socket!".getBytes(); os.write(data); } finally{ sc.close(); os.close(); } } catch(IOException x){ x.printStackTrace(); }
SocketConnection устанавливает соединение с 9002 портом локальной машины. Если соединение успешно установлено, OutputStream записывает сообщение в исходящий поток. Обратите внимание, для передачи данных сокетам нужен канал данных. В случае если клиентское приложение не запущенно и не прослушивает порт, программа вернет ошибку.
В следующем примере описывается получение данных с помощью MIDP HttpConnection. Обратите внимание на то, что интерфейс HttpConnection является частью профиля MIDP, а не CLDC. HttpConnection совмещает в себе InputStream и OutputStream. Каждый HttpConnection может открыть и использовать только один InputStream и один OutputStream. Также важен порядок в котором используются потоки. OutputStream должен использоваться перед InputStream (если конечно вы собираетесь использовать InputStream). После того как поток данных использован, соединение должно быть закрыто и, если это необходимо, создано новое. Эта схема соответствует HTTP парадигме запрос-ответ.
HttpConnection намного сложнее сокетов и дейтаграмм. HTTP соединение может находиться в одном из трех состояний.
Сразу после открытия соединения HttpConnection находится в состоянии "устанавливается". На этой стадии можно задать различные параметры, например тип запроса (GET/POST) или настройки HEAD заголовка (с помощью методов setRequestMethod() и setRequestProperty()).
В состояние "соединено" поток переходит в результате вызова любого из методов, которые приводят к передачи данных на сервер:
После того как поток перешел в состояние "соединено" вызов методов setRequestMethod() и setRequestProperty() приведет к возникновению IOException.
Приведенный ниже пример демонстрирует считывание данных из HttpConnection. Поскольку порт не указан, подключение происходит к 80 порту. По умолчанию для запроса используется метод GET.
HttpConnection c=null; InputStream is=null; StringBuffer sb=new StringBuffer(); try{ c=(HttpConnection)Connector.open( "http://www.mobilab.ru", Connector.READ_WRITE,true); c.setRequestMethod(HttpConnection.GET);//default is= c.openInputStream();// transition to connected! int ch=0; for(int ccnt=0; ccnt<150; ccnt++){// get the title. ch= is.read(); if(ch==-1){ break; } sb.append((char)ch); } } catch(IOException x){ x.printStackTrace(); } finally{ try{ is.close(); c.close(); } catch(IOException x){ x.printStackTrace(); } } System.out.println(sb.toString());
В этом примере устанавливается соединение с сервером www.mobilab.ru. Поскольку используется HttpConnection и не указан конкретный порт, используется принятый по умолчанию порт 80. Для запроса используется метод GET (вообще-то метод GET используется по-умолчанию и его установка в данном примере носит иллюстративный характер.
Как Вы убедились, GCF содержит достаточно богатый арсенал сетевых возможностей. Вы должны четко представлять, какой из способов соединения оптимально подходит для Вашей задачи. Спецификация MIDP 1.0 требует только наличия поддержки HTTP, хотя многие устройства поддерживают работу с дейтаграммами и сокетами. Прежде чем их использовать, нужно убедиться, что они действительно поддерживаются телефоном.
Использование протокола HTTP является достаточно удачным решением, поскольку он гарантированно поддерживается всеми телефонами и, кроме того, используемый по умолчанию порт 80 практически не блокируется файерволлами. Недостатком данного протокола является его перегруженность. Не забывайте, что пользователь платит за трафик, поэтому чем меньше данных Вы передаете, тем лучше.
Дейтаграммы достаточно аскетичны и наиболее предпочтительны с точки зрения экономия пользовательского бюджета. Не забывайте, что в случае использования дейтаграмм Вы сами должны позаботиться о проверке получения вашего сообщения на другой стороне и обеспечить механизм восстановления последовательности передаваемых пакетов.
Сокеты находятся где-то между дейтаграммами и HTTP протоколом. Протокол полностью берет на себя управление и контроль за потоком данных, но требует меньше сетевых ресурсов по сравнению с HTTP. Это означает, что приложение не заботится о том, получены ли данные на другом конце соединения.
GCF предоставляет богатые функциональные возможности и определяет правила работы с сетевыми соединениями в рамках J2ME архитектуры. GCF легко расширяется и производители телефонов могут добавлять поддержку своих протоколов и других типов соединения. В некоторых случаях производитель может использовать GCF для поддержки потока собственного мультимедийного контента со специальным типом соединения или для установления соединения с GPS ресивером. Эта гибкость позволяет производителям не ограничиваться стандартным набором протоколов, а развивать новые технологии.
Master Networking in J2ME for Well-connected Mobile AppsDavid Hemphill.
Перевод:aRix.