Java 2 Micro Edition (J2ME)

       

Соединения coкeтa


Соединения сокета являются последним типом соединений, явно представленных сетевой инфраструктурой MIDP. Реализации MIDP, поддерживающие сокеты, реализуют традиционные сокеты стиля UNIX. Стоит напомнить еще раз, что от реализаций не требуется поддерживать какой-либо механизм соединения, кроме HTTP 1.1. Многие из них не хотят поддерживать соединения сокета.

Интерфейс StreamConnectionNotifier представляет известный сокет сервера. Интерфейс StreamConnection, который вы видели ранее, представляет сокет клиента.

Сокет - это сетевой механизм транспортного уровня, который обычно реализует пару протоколов TCP/IP в фиксированных межсетевых средах. Сокет сервера является программой, предоставляющей сетевую службу для соединений через сокеты.

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

Соединения сокета находятся на транспортном уровне. Их поддержка осуществляется автоматически, если сокеты реализованы с помощью соединений TCP. TCP является ориентированным на соединения протоколом транспортного уровня, предназначенным для хранения данных в течение нескольких пересылок между клиентом и сервером.

Однако сокеты не всегда реализуются с помощью TCP/IP. Тем не менее, поскольку TCP/IP является стандартной парой протоколов Интернета транспортного и сетевого уровня, системы, которые реализуют сокеты с помощью других механизмов, должны связываться с Интернет-узлами с помощью шлюза. Это требование действует как в среде фиксированных сетей, так и в беспроводном Интернете.

В настоящее время TCP/IP не поддерживается многими беспроводными сетями. Тем не менее, беспроводные сети все равно могут поддерживать соединения сокета. Они могут подчиняться интерфейсу сокета и создавать такие же связанные с соединением абстракции, что и TCP/IP, используя другие протоколы - даже собственные. Однако если транспортировщик использует нестандартный набор протоколов, они будут иметь шлюз, который свяжет их беспроводную сеть с внешним миром.


Протоколы уровня приложений могут быть определены поверх протоколов транспортного уровня, если это необходимо. Реализация протокола уровня приложений использует любой доступный механизм транспортировки. Например, HTTP является протоколом уровня приложений. Создатели приложения MIDP могут выбирать, не создать ли протокол уровня приложений непосредственно поверх механизма сокета, если таковой поддерживается. Если сокеты не поддерживаются, сообщения протокола уровня приложений могут быть туннелированы с помощью HTTP. Протокол уровня приложений ответственен за определение своего собственного состояния, которое отличается от состояния протокола транспортного уровня.

Модель соединения сокета. Соединения сокета устанавливаются также, как и другие типы соединений, клиенты используют метод Connector.open() и указывают URI базирующейся на сокетах службы, с которой они хотят соединиться. Однако со стороны сервера модель соединения немного отличается из-за определяемой соединениями природы сокетов. Эта модель необходима для серверов, чтобы иметь возможность обеспечивать многочисленные одновременные соединения клиентов.

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

  • Демон сервера устанавливает соединение, которое связано с известным сокетом - сокетом сервера, чей порт и служба были предварительно установлены и объявлены.


  • Демон сервера прослушивает запросы соединения клиента.




  • Клиент создает запрос соединения для демона сервера и ожидает отклика.


  • Демон принимает запрос соединения и создает новое соединение с клиентом, сервер связывает соединение с новым сокетом. Сервер создает новый объект приложения, который будет взаимодействовать с клиентом через новое соединение и порождает нить для контролирования этого объекта.


  • Запрос соединения клиента возвращается. Приложение клиента теперь имеет объект соединения, чьей конечной точкой является новый сокет, созданный сервером.




  • Клиент и сервер взаимодействуют через новое соединение.


  • Демон сервера продолжает прослушивать последующие запросы соединения на известном сокете.


  • На рисунке 8.5 показано схематичное представление этого процесса. Порядок этапов в вышеописанном списке соответствует порядку, показанному на рисунке 8.5.



    Рисунок 8.5. Базирующиеся на сокетах службы должны быть способны выполнять асинхронную обработку. Демон порождает нить для контролирования взаимодействия с каждым клиентом

    Согласно соглашению, известный сокет использует предварительно определенный порт для установления соединений с клиентами. Использование определенного порта определенной службой уникально для каждой службы - сокета, дейтаграммы и так далее. Клиенты, таким образом, знают, как достичь соединения с желаемым сервером для запроса соединения.

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

    Идиома открытия сокетов очень сходна с идиомой открытия дейтаграмм. Приложения пересылают URI в метод создания Connector.open() для получения соединения. Синтаксис LJRI следующий:

    address := <протокол>://<адресат>

    protocol := "socket"

    target := [<хост>]:<порт>

    host := Оначимое DNS-имя хоста или его номер>

    port := оначимый системный номер порта>

    Еще раз повторюсь, присутствие или отсутствие имени компьютера в URI говорит о том, является ли соединение серверным или клиентским. Демон сервера сначала открывает соединение на своем известном сокете, как показано в следующем примере:

    StreamConnectionNotifier wellKnown =

    Connector.open("socket://:98765");



    Спецификация MIDP также позволяет использовать схему serversocket для серверных соединений. Эта последняя схема может быть выгодна, поскольку явное использование serversocket в части кода делает более очевидным для кого-либо, читающего код, то, что серверное соединение установлено. Следующая строка кода демонстрирует использование схемы serversocket:

    StreamConnectionNotifier wellKnown =

    Connector.open("serversocket://:98765");

    Класс StreamConnectionNotifier является MIDP эквивалентом класса Java.net.Serversocket платформы J2SE. StreamConnectionNotifier является на самом деле сокетом сервера.

    Оба вышеуказанных оператора возвращают объект соединения, который представляет собой соединение с известным сокетом. Сервер затем прослушивает соединение на предмет запросов соединения от клиентов с помощью оператора, подобного нижеследующему:

    StreamConnection clientConn = vellKnovn.acceptAndOpen();

    Этот оператор блокирует операции до тех пор, пока не появится запрос клиента на соединение. При появлении запроса клиента на соединение метод acceptAndOpen () обрабатывает запрос перед передачей управления. Чтобы обработать запрос на соединение, он

  • Принимает запрос на соединение;


  • Создает новый объект соединения;


  • Связывает соединение с неиспользуемым сокетом;


  • Уведомляет клиента о новом соединении сокета.


  • Эти этапы объясняют название StreamConnectionNotifier. Демон сервера будет «уведомлен» о запросе на соединение при возвращении вызова блокирующего acceptAndOpen (). И он уведомляет клиента о том, что он должен прослушивать новый сокет, установленный для взаимодействия клиент-сервер. В таблице 8.13 показан единственный метод интерфейса StreamConnectionNotifier.

    Таблица 8.13. Методы интерфейса StreamConnectionNotifier

    Метод StreamConnectionNotifier Описание
    StreamConnection acceptAndOpen () Возвращает новый потоковый обьект, связанный с новым сокетом и соединенный с запрашивающим клиентом
    Клиенты запрашивают соединение у известного сокета, создавая клиентский запрос соединения в стандартной форме. Например, следующий оператор представляет клиентский запрос соединения:



    StreamConnection conn =

    Connector.open("socket://server.foo.com:98765");

    Клиенты должны включать имя сервера, поддерживающего службу; номер порта представляет известный сокет сервера. Клиенты, которые хотят соединиться со службой на локальной машине, могут использовать обозначение localhost для сервера, как показано в следующем вызове:

    StreamConnection conn =

    Connector.open("socket://localhost:98765");

    Оба вызова StreamConnectionNotifier.acceptAndOpen(} сервера и Connector.open() клиента создают объект StreamConnection. Вы уже видели класс StreamConnection при нашем обсуждении коммуникационных портов.

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

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

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



    В листингах 8.6 - 8. 8 демонстрируется набор классов, которые составляют структуру сокетных коммуникаций в MIDP. Смысл заключается в том, что эти классы будут использоваться приложением, которое нуждается в сокетных коммуникациях. Эти примеры составляют не больше чем основу, которая формирует базовую структуру поддержки сокетных взаимодействий. Они не являются функционирующими приложениями.

    Некоторые данные были проигнорированы в этом коде. Например, сама сетевая служба не определена, нет определения синтаксиса или семантики сообщения протокола уровня приложений. Кроме того, код не обращается к очистке рабочих нитей со стороны сервера. Следующие классы являются классами, составляющими данный пример:

  • ServerSocket - определяет демон сервера, который прослушивает известный сокет на предмет клиентских запросов соединения.


  • Server Agent - определяет объект, один экземпляр которого демон создает для каждого клиентского запроса. Каждый экземпляр взаимодействует с клиентом. Данный класс определяет действительную службу.


  • ClientSocket - представляет клиента.


  • Листинг 8.6. Сервер порождает новую нить для создания объекта со стороны сервера, который взаимодействует с каждым клиентом. Клиент и сервер должны определять семантику своих сообщений

    import javax.microedition.io.Connector;

    import javax.microedition.io.StreamConnection;

    import javax.microedition.io.StreamConnectionNotifier;

    import Java.io.lOException;

    /**

    Данный класс реализует службу, которая прослушивает запросы

    клиентских соединений на известном сокете.

    Он открывает соединение на предварительно определенном номере порта.

    А затем блокирует обработку на данном порте,

    ожидая клиентского запроса соединения.

    Когда запрос появляется, он принимает его и открывает новое

    соединение сокета. Эти два этапа выражаются в реализации,

    уведомляющей реализацию клиента о новом соединении сокета.

    Этот сервер затем порождает компонент и передает его новому

    объекту соединения. Компонент запускает отдельную нить. Компонент



    теперь свободен для взаимодействия с клиентом асинхронно

    от продолжающейся работы сервера.

    public class ServerSocket imlements Runnable

    {

    // Порт по умолчанию, на котором установлен известный

    // сокет. public static final String DEFAULT_PORT = "9876";

    // Порт, на котором установлен известный

    // сокет. protected String wellKnownPort;

    // URI, который данный сервер использует для открытия своего

    // известного сокета. protected String uri;

    // Соединение с известным сокетом.

    protected StreamConnectionNotifier wellKnownConn;

    // Соединение сокета, которое соединяется с клиентом,

    protected StreamConnection clientConn;

    /**

    Конструктор для подклассов.

    */

    protected ServerSocket()

    super ();

    /**

    Конструктор.

    @param port Известный порт, на котором устанавливается

    этот объект как блок прослушивания.

    */

    public ServerSocket (String port)

    }

    thisl); if (port == null)

    {

    wellKnownPort = DEFAULT_PORT;

    }

    else

    }

    wellKnownPort = port;

    }

    setURI(port);

    {

    protected void setURI(String port)

    {

    StringBuffer buf = new StringBuffer("socket://:");

    buf.append(port); uri = buf.toString();

    }

    /**

    Запустите данный сервер. Этот метод должен быть

    вызван явно после создания данного объекта. Он запускает

    прослушивание запросов клиентов на известном сокете.

    Оператор вызова должен запустить это выполнение в отдельной нити.

    */

    public void run()

    {

    while (true)

    {

    try

    {

    // Откройте соединение известного сокета для данной

    // «службы». wellKnownConn = (StreamConnectionNotifier)

    Connector.open(uri);

    //Прослушиваем запросы соединения. Данный вызов

    // блокирует работу до тех пор, пока не будет получен

    // запрос на соединение.

    clientConn = wellKnownConn.acceptAndOpen()

    // Создадим экземпляр агента»сервера, объект, который

    // представляет службу для клиента. Каждый экземпляр

    // взаимодействует с одним клиентом.

    // Порождаем нить для взаимодействия с

    // клиентом, создавшим запрос на соединение.

    ServerAgent agent = new ServerAgent(clientConn);



    Thread thread = new Thread (agent);

    } catch (lOException ioe)

    ( System.out.printlnfioe.getMessage!));

    ioe.printStackTrace(); break;

    )

    }

    }

    }

    Листинг 8.7. Агент сервера является объектом, который взаимодействует с клиентом независимо от демона сервера. Он запускает свою собственную нить, позволяя другим экземплярам одновременно взаимодействовать со своими клиентами

    import javax .microedition. io._StreamConnectior.;

    /**

    Данный класс определяет компоненту, которую сервер

    создает для взаимодействия с клиентом.

    Он действует как «агент» от имени сервера для того, чтобы сервер

    был свободен для прослушивания только новых запросов соединения.

    Экземпляры данного класса являются частью сервера.

    */

    public class ServerAgent implements Runnable

    private StreamConnection conn;

    /**

    Конструктор.

    @param с Объект соединения, который представляет

    соединение с клиентом. Класс ServerSocket создает и пересылает

    его в данный конструктор.

    */

    public ServerAgent(StreamConnection c)

    super (); conn = с;

    }

    /**

    Выполняется агент данного сервера. Начинается диалог с клиентом. Этот метод должен быть вызван явно после того, как создан данный объект.

    public void run()

    }

    // Взаимодействует с клиентом. Реализует поведение,

    // которое определяет данную службу.

    }

    }

    Листинг 8.8. Клиент имеет отдельно соединение с агентом сервера. Модель состояния взаимодействий, а также синтаксис и семантика взаимодействий определяются сервером, но клиенты должны им подчиняться

    import javax.microedition.midlet.MI Diet;

    import javax.microedition.io.StreamConnection;

    import javax.microedition.io.Connector;

    import Java.io.lOException;

    /**

    Данный класс реализует клиента, который соединяется с сервером.

    Для создания экземпляра данного класса вы должны указать сервер

    (имя сервера DNS) и известный порт службы, с которой вы хотите

    установить соединение.

    */

    public class ClientSocket implements Runnable

    {

    public static final String P.ROTOCOL = "socket";

    /'/ Порт известного сокета сервера, private String serverPort;



    // Имя сервера, с которым соединяемся, private String serverHostName;

    // URI известного серверного сокета. private String serverURI;

    // Соединение с.сервером.

    private StreamConnection streamConn;

    protected ClientSocket()

    }

    super();

    }

    /**

    Открытый конструктор. Вы должны указать имя сервера DNS

    и номер порта службы. @param server - имя DNS машины,

    с которой вы хотите соединиться.

    @param port - Номер порта сервера, с которым вы хотите соединиться.

    */

    public ClientSocket(String server, String port)

    throws TOException

    (

    this();serverHostName = server; serverPort = port;

    serverURI = buildServerURI (); open () ;

    }

    /**

    Конструктор.

    @param uri - полностью сформированный URI службы,

    запрос на соединение с которой вы хотите создать.

    @сбрасывает InvalidArgumentException при несформированном URI.

    */

    public ClientSocket(String uri) throws lOException

    {

    this (); serverURI = uri;

    }

    Открывает соединение. После того как создан данный объект,

    соединение с сервером еще не открыто. Вы должны открыть его явно.

    Это делает модель использования более гибкой для клиентов.

    @ сбрасывает lOException, если соединение не может быть

    открыто по некоторым причинам.

    */

    public void open() throws lOException

    streamConn = (StreamConnection) Connector.open(serverURI);

    /**

    Закрывает соединение с сервером.

    */

    public void closed try streamConn. closed ; }

    catch (lOException ioe)

    }

    ioe.printStackTraced ;

    {

    {

    /**

    Выполняет клиентское взаимодействие.

    Запускает посылку клиентом запросов на сервер.

    Этот метод должен быть вызван после того, как метод opend установит соединение.

    */

    public void run ()

    {

    // Начинаем взаимодействие с сервером.

    // Посылаем запросы, читаем ответы

    ....

    private String buildServerURI ()

    }

    StringBuffex uri = new StringBuffer(PROTOCOL);

    uri.append ("://"); uri.append(serverHostName);

    uri.append(":"); uri.append(serverPort); return uri.toString ();

    }

    }

    Использование соединений сокета в приложениях MIDP. Естественно, тот факт, что интерфейс StreamConnectionNotif ier определен как часть пакета IOMIDP, предполагает, что он должен использоваться приложениями, запускаемыми на устройствах MIDP. Это означает, что MID-лет может поддерживать открытое соединение с известным соке-том для использования клиентами. Клиенты, однако, могут находиться в другом месте.



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

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

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

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

    Независимо от того, являются ли адреса мобильного устройства статическими или динамическими, сеть транспортировщика может задействовать какую-либо схему трансляции сетевого адреса (network address translation (NAT)) для изменения или преобразования адреса мобильного устройства. Мотив использования схемы NAT может быть связан с ограничениями места или безопасности. Определенные сетевые протоколы могут не иметь достаточного адресного места для обработки всех цифр сетевых устройств. Если это так, и если транспортировщик желает узнать адреса своих устройств, ему придется предоставить какой-либо реестр для отображения динамических адресов и механизм поиска. В противном случае ваше серверное приложение не будет доступно.



    По причинам безопасности транспортировщики могут не захотеть выставлять адреса мобильных устройств своих пользователей на всеобщее обозрение. Если это так, ваше приложение может быть доступно только приложениям, работающим на системах транспортировщика. Более того, доступ может быть ограничен до привилегированных приложений, даже в сети транспортировщика. И даже в сети каждому устройству придется иметь способ объявления своего сетевого адреса другим устройствам для соединения с ним. В большинстве современных поколений беспроводных сетей мобильные устройства не осведомлены о наличии или адресе друг друга. Это может измениться в сетях поколения 3G, которые должны распространиться более широко в ближайшие несколько лет.

    Беспроводные сети 3G двигаются в сторону принятия IPv6 как своих протоколов сетевого уровня. В IPv6 существует множество адресов, что делает возможным назначение уникального IP-адреса каждому мобильному устройству в мире. Если каждое устройство имеет уникальный статический IP-адрес, любое приложение, которое знает адрес вашего устройства, может соединиться с известным портом вашего устройства.

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


    Содержание раздела